
static char rcsid[] = "@(#)$Id: aliasdb.c,v 4.1.1.1 90/06/21 23:21:47 syd Exp $";

/*******************************************************************************
 *  The Elm Mail System  -  $Revision: 4.1.1.1 $   $State: Exp $
 *
 * 			Copyright (c) 1986, 1987 Dave Taylor
 * 			Copyright (c) 1988, 1989, 1990 USENET Community Trust
 *******************************************************************************
 * Bug reports, patches, comments, suggestions should be sent to:
 *
 *	Syd Weinstein, Elm Coordinator
 *	elm@DSI.COM			dsinc!elm
 *
 *******************************************************************************
 * $Log:	aliasdb.c,v $
 * Revision 4.1.1.1  90/06/21  23:21:47  syd
 * Add missing check for eof and for failure of scanf
 * From: Ross Johnson
 * 
 * Revision 4.1  90/04/28  22:42:28  syd
 * checkin of Elm 2.3 as of Release PL0
 * 
 *
 ******************************************************************************/

/** Alias database files...

**/


#include "headers.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>

extern int errno;

#ifdef USE_DBM
# include <dbm.h>
#endif

#define  absolute(x)		((x) > 0 ? x : -(x))

char *find_path_to(), *strcat(), *strcpy();
unsigned long sleep();

#ifndef DONT_TOUCH_ADDRESSES
int  findnode_has_been_initialized = FALSE;
#endif

findnode(name, display_error)
char *name;
int   display_error;
{
	/** break 'name' into machine!user or user@machine and then
	    see if you can find 'machine' in the path database..
	    If so, return name as the expanded address.  If not,
	    return what was given to us!   If display_error, then
	    do so...
	**/

#ifndef DONT_TOUCH_ADDRESSES
	
	char   old_name[SLEN];
	char   address[SLEN];

	if (strlen(name) == 0)
	  return;
	
	if (! findnode_has_been_initialized) {
	  if (warnings)
	    error("Initializing internal tables...");
#ifndef USE_DBM
	  get_connections();
	  open_domain_file();
#endif
	  init_findnode();
	  clear_error();
          findnode_has_been_initialized = TRUE;
	}

	strcpy(old_name, name);		/* save what we were given */

	if (expand_site(name, address) == -1) {
          if (display_error && name[0] != '!') {
	    dprint(3, (debugfile, "Couldn't expand host %s in address.\n",
	             name));
	    if (! check_only && warnings) {
	      error1("Couldn't expand system %s.", name);
	      sleep(1);
	    }
	  }
	  strcpy(name, old_name);	/* and restore... */
	}
	else
	  strcpy(name, address);
#endif
	return;
}

#if defined(OPTIMIZE_RETURN) || !defined(DONT_TOUCH_ADDRESSES)
int
expand_site(cryptic, expanded)
char *cryptic, *expanded;
{

	/** Given an address of the form 'xyz@site' or 'site!xyz'
	    return an address of the form <expanded address for site>
            with 'xyz' embedded according to the path database entry.
	    Note that 'xyz' can be eiher a simple address (as in "joe")
	    or a complex address (as in "joe%xerox.parc@Xerox.ARPA")!
	    0 = found, -1 return means unknown site code 
	
	    Modified to strip out parenthetical comments...
	**/

#ifdef ACSNET

	strcpy(expanded, cryptic);	/* fast and simple */
	return(0);

#else
# ifdef USE_DBM
	datum  key, contents;
# endif

	char   name[VERY_LONG_STRING], sitename[VERY_LONG_STRING], 
               temp[VERY_LONG_STRING], old_name[VERY_LONG_STRING],
	       comment[LONG_STRING];
	char   *expand_domain(), *addr;
	register int i = 0, j = 0, in_parens = 0, domain_name;

	strcpy(old_name, cryptic);	/* remember what we were given */

	/** break down **/

	/** first, rip out the comment, if any, noting nested parens **/

	if ((i = chloc(cryptic, '(')) > -1) {
	  comment[j++] = ' ';			/* leading space */
	  do {
  	    switch(comment[j++] = cryptic[i++]) {
	    case '(':	in_parens++;
			break;
	    case ')':	in_parens--;
			break;
	    }
	  } while(in_parens && cryptic[i] != '\0');
	  comment[j] = '\0';

	  /* and remove this from cryptic string too... */
	  if (cryptic[(j = chloc(cryptic,'('))-1] == ' ')
	    cryptic[j-1] = '\0';
	  else
	    cryptic[j] = '\0';
	}
	else
	  comment[0] = '\0';

	i = j = 0;	/* reset */

	while (cryptic[i] != AT_SIGN && cryptic[i] != BANG && 
	       cryptic[i] != '\0' && cryptic[i] != '(')
	  sitename[j++] = cryptic[i++];

	sitename[j++] = '\0';

	j = 0;
	
	if (cryptic[i] == '\0') return(-1);	/* nothing to expand! */

	domain_name = (cryptic[i] == AT_SIGN);

	i++;

	while (cryptic[i] != '\0' && cryptic[i] != '(' && 
               ! whitespace(cryptic[i]))
	  name[j++] = cryptic[i++];

	name[j] = '\0';

	if (domain_name) {
	  strcpy(temp, name);
	  strcpy(name, sitename);
	  strcpy(sitename, temp);
	}

	dprint(5, (debugfile, "\nBroke address into '%s' @ '%s' '%s'\n\n",
		name, sitename, comment));

#ifdef USE_DBM

	if (size_of_pathfd == 0)
	  return(-1);

	key.dptr  = sitename;
	key.dsize = strlen(sitename) + 1;

	contents = fetch(key);

	if (contents.dptr == 0) 
	  return(-1);			/* can't find it! */

	sprintf(expanded, contents.dptr, name);
	strcat(expanded, " ");			/* add a single space... */
	strcat(expanded, comment);		/*    ...and add comment */
	return(0);
#else

#ifndef LOOK_CLOSE_AFTER_SEARCH

	if (talk_to(sitename)) {
	  strcpy(expanded, old_name);	/* restore! */
	  return(0);
	}
#endif

	if ((addr = find_path_to(sitename, TRUE)) == NULL) {

#ifdef LOOK_CLOSE_AFTER_SEARCH

	    if (talk_to(sitename)) {
	      strcpy(expanded, old_name);	/* restore! */
	      return(0);
	    }
	    else
#endif
	    if ((addr = expand_domain(cryptic)) != NULL) {
	       strcpy(expanded, addr);	/* into THIS buffer */
	       strcat(expanded, comment);	/* patch in comment */
	       return(0);
	    }
	    else  if (size_of_pathfd == 0) {	/* no path database! */
	      strcpy(expanded, old_name);	/* restore! */
	      return(0);
	    }
	    else {			     /* We just can't get there! */
	      strcpy(expanded, old_name);	/* restore! */
	      return(-1);
	    }
	}
	else {			/* search succeeded */
	   sprintf(expanded, addr, name);
	   strcat(expanded, comment);		/* add comment */
	   return(0);
	}
#endif
#endif
}
#endif /* defined(OPTIMIZE_RETURN) || !defined(DONT_TOUCH_ADDRESSES) */

int
binary_search(name, address)
char *name, *address;
{
	/* binary search file for name.  Return 0 if found, -1 if not */

	char machine[40];
	register long first = 0, last, middle;
	register int  compare;

	address[0] = '\0';

	last = size_of_pathfd;

	do {

	  middle = (long) ((first+last) / 2);

	  get_entry(machine, address, pathfd, middle);

	  compare = strcmp(name, machine);

	  if (compare < 0) 
	    last = middle - 1;
	  else if (compare == 0)
	    return(0);
	  else  /* greater */
	    first = middle + 1; 
	} while (absolute(last) - absolute(first) > FIND_DELTA);

	/* It could be that our target entry lies exactly at `first'.
	 * Since get_entry() compares at the entry beginning after
	 * the passed offset (unless it's 0), we need to decrement it
	 * (unless it's 0), and give it one more try.
	 */
	get_entry(machine, address, pathfd, (first == 0L ? first : --first));
	compare = strcmp(name, machine);
	return(compare == 0 ? 0 : -1);
}

get_entry(machine, address, fileid, offset)
char *machine, *address;
FILE *fileid;
long offset;
{
	int	ch;

	/** get entry...return machine and address immediately
	    following given offset in fileid.  **/

	(void) fseek(fileid, offset, 0);

	/* To get to the beginning of a record, if we are not at offset 0,
	 * read until we hit an end-of-line */

	if(offset != 0L)
	  while ((ch = getc(fileid)) != EOF)
	    if (ch == '\n')
	      break;

	*machine = '\0';
	*address = '\0';
	fscanf(fileid, "%s\t%s", machine, address);
}

#ifndef DONT_TOUCH_ADDRESSES
init_findnode()
{
	/** Initialize the FILE and 'size_of_file' values for the 
	    findnode procedure **/

	struct stat buffer;
	char   *path_filename;

#ifdef USE_DBM
	char buf[BUFSIZ];

	sprintf(buf,"%s.pag", pathfile);
	path_filename = buf;
#else
	path_filename = pathfile;
#endif

	if (stat(path_filename, &buffer) == -1) {
	  dprint(2, (debugfile, 
		  "Warning: pathalias file \"%s\" wasn't found by %s\n", 
		  path_filename, "init_findnode"));
	  size_of_pathfd = 0;
	  return;
	}

	size_of_pathfd = (long) buffer.st_size;

#ifdef USE_DBM
	
	if (dbminit(pathfile) != 0) {
	  dprint(2, (debugfile,
		 "Warning: couldn't initialize DBM database %s\n", 
		 pathfile));
	  dprint(2, (debugfile, "** %s - %s **\n\n", error_name(errno),
		     error_description(errno)));
	  size_of_pathfd = 0;	/* error flag, in this case */
	  return;
	}
 	
	return;
#else

	if ((pathfd = fopen(pathfile,"r")) == NULL) {
	  dprint(2, (debugfile,
		"Warning: Can't read pathalias file \"%s\" within %s\n", 
		   pathfile, "init_findnode"));
	  size_of_pathfd = 0;
	}
	else
	  dprint(3, (debugfile, "\nOpened '%s' as pathalias database.\n\n", 
		  pathfile));
#endif
}
#endif /* DONT_TOUCH_ADDRESSES */

char *find_path_to(machine, printf_format)
char *machine;
int   printf_format;
{
	/** Returns either the path to the specified machine or NULL if
	    not found.  If "printf_format" is TRUE, then it leaves the
	    '%s' intact, otherwise it assumes that the address is a uucp
	    address for the domain expansion program and removes the
	    last three characters of the expanded name ("!%s") since
	    they're redundant with the expansion!
        **/

	static char buffer[SLEN];	/* space for path */

	if (size_of_pathfd > 0)
	  if (binary_search(machine, buffer) != -1) {   	/* found it! */
	    if (! printf_format && strlen(buffer) > 3)
	      buffer[strlen(buffer)-3] = '\0';
	    return( (char *) buffer);
	  }

	return(NULL);			            /* failed if it's here! */
}
