/***************************************************************************/
/*  Module:      $Id: config.c,v 1.3 1995/05/24 00:44:37 maf Exp $
/*  Description: Configuration file and alias functions for sendpage
/*  Author:      maf
/*
/* Copyright (c) 1995 Mark Fullmer and The Ohio State University
/***************************************************************************/

/*
$Log: config.c,v $
 * Revision 1.3  1995/05/24  00:44:37  maf
 * termio -> termios
 * pcinfo.modeminit not terminated right
 * all unknown string sizes use %.512s for report()
 * casts for picky Solaris compiler
 *
 * Revision 1.2  1995/03/15  04:40:46  maf
 * *** empty log message ***
 *
 * Revision 1.1  1995/01/10  01:42:09  maf
 * Initial revision
 *
*/

#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <limits.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <syslog.h>
#include <stdlib.h>
#include <unistd.h>
#include <db.h>
#include "report.h"
#include "sendpage.h"

#ifdef NEED_MALLOC_H
#include <malloc.h>
#endif


/*********************************************************************/
/* Function: ReadConfig
/*	reads sendpage configuration file, updating the paging central
/*	and alias database.
/*
/* Returns: 0	good	- aliasdb and pcdb point to a new database reflecting
/*							the contents of the configuration file.  if aliasdb
/*							or pcdb were passed as non null pointers, the old
/*							databases are closed.
/*						- ncentral is updated with the number of paging
/*							central entries stored - currently this is
/*							limited to the PET protocol.  ncentral is left
/*							untouched on error.
/*
/*			!0	bad		- if aliasdb pcdb were passed as non null pointers, 
/*							they are left untouched.
/*
/*	The initial call to ReadConfig() should have aliasdb and pcdb be
/*	null pointers.
/*
/*	The caller is responsible for closing pcdb and aliasdb
/*
/*	error reporting is done via report()
/*
/*********************************************************************/

ReadConfig(aliasdb, pcdb, ncentral, fname)
DB **aliasdb, **pcdb;
u_int *ncentral;
char *fname;
{

	int r, fd, lineno;
	struct cbuf cbuf;
	int err;
	extern int errno;
	int where, type;
	int x, cferr;
	struct dynstring keystring, datastring;
	u_int naliases, npets;
	DBT dbkey, dbval;
	DB *tpcdb, *taliasdb; 
	u_int tncentral;
	char nullbyte;
	char uia[6];	/* unsigned integer in ascii 65536 (5) + null (1) = 6 */
	struct pcinfo pcinfo;
	char *c, *c2, tmpstr[PCINFO_STR_LEN];
	

	fd = -1;			/* unused */
	err = 1;			/* bad */
	where = 0;			/* field in type */
	type = CF_UNKNOWN;	/* what is it */
	cferr = 0;			/* no errors (yet) parsing config file */
	lineno = 1;			/* people count from 1 */
	naliases = 0;		/* # of aliases loaded */
	npets = 0;			/* # of pet protocol defs loaded */
	tpcdb = (DB*)0;		/* database not opened */
	taliasdb = (DB*)0;	/* database not opened */
	nullbyte = 0;		/* null byte */
	tncentral = 0;		/* none */

	bzero(&keystring, sizeof keystring);
	bzero(&datastring, sizeof datastring);
	bzero(&cbuf, sizeof cbuf);


	if (!(tpcdb = (DB*)dbopen((char*)0, O_CREAT|O_RDWR|O_TRUNC,
		S_IRUSR|S_IWUSR, DB_HASH, (void*)0))) {
		report (LOG_ERR, "dbopen(tpcdb) failed");
		goto ReadConfigout;
	}

	if (!(taliasdb = (DB*)dbopen((char*)0, O_CREAT|O_RDWR|O_TRUNC,
		S_IRUSR|S_IWUSR, DB_HASH, (void*)0))) {
		report (LOG_ERR, "dbopen(aliasdb) failed");
		goto ReadConfigout;
	}

	if ((fd = open(fname, O_RDONLY, 0)) == -1) {
		report(LOG_ERR, "open (%s): %.512s", fname, strerror(errno));
		goto ReadConfigout;
	}


	while (!cferr) {

		if ((r = GetNextField(fd, &cbuf, &lineno)) == -1) {
			report(LOG_ERR, "GetNextField() returned error condition");
			goto ReadConfigout;
		}

		++ where;

		c = (char*) (cbuf.buf+cbuf.start);

		switch (type) {

			case CF_UNKNOWN:

				where = 0; /* 0th field */

				/* skip blank and comment lines */
				if (!cbuf.len)
					break;

				if ((cbuf.len == 2) && (!strncmp(c, "pc", 2)))
					type = CF_PC;
				else if ((cbuf.len == 5) && (!strncmp(c, "alias", 5)))
					type = CF_ALIAS;
				else
					cferr = 1;
				break;

			case CF_PC:

				switch (where) {

					case 1:		/* first field, init the struct */

						pcinfo.id = tncentral;
						pcinfo.dialer = DIALER_INTERNAL;
						pcinfo.name[0] = 0;
						pcinfo.phone[0] = 0;
						strcpy(pcinfo.password, "000000");
						strcpy(pcinfo.modeminit, MODEM_INIT);
						strcpy(pcinfo.modemdev, MODEM_DEV);
						strcpy(pcinfo.modemdial, MODEM_DIAL);
						pcinfo.answertime = 25;
						pcinfo.parity = PARENB;
						pcinfo.speed = B1200;
						pcinfo.databits = CS7;
						pcinfo.protocol = PC_PET1;
						pcinfo.msgretries = 30;
						pcinfo.maxmsgsize = 80;

					default: /* handle the option */

						/* check if this line is done */
						if (!cbuf.len) { 

							/* reset for next line */
							type = 0; 

							/* sanity check */
							if ((!pcinfo.name[0]) || (!pcinfo.phone[0])) {
								report(LOG_ERR,
									"must set name and phone number");
								cferr = 1;
								break;
							}

							++npets;
							++tncentral;

							dbval.data = (void*)&pcinfo;
							dbval.size = sizeof(struct pcinfo);

							dbkey.data = (void*)pcinfo.name;
							dbkey.size = strlen(pcinfo.name);

							if (tpcdb->put(tpcdb, &dbkey, &dbval,
								(u_int)R_NOOVERWRITE)) {
								report(LOG_ERR,
									"db->put() failed - line %d, errno=%d",
									lineno, errno);
								cferr = 1;
								break;
							}

							break;
						}

						if ((c2 = CheckTagField(c, cbuf.len, "name="))) {
							memcpy(pcinfo.name, c2, cbuf.len - 5);
							pcinfo.name[cbuf.len-5] = 0;
						} else if ((c2 = CheckTagField(c, cbuf.len,
							"phone="))) {
							memcpy(pcinfo.phone, c2, cbuf.len - 6);
							pcinfo.phone[cbuf.len-6] = 0;
						} else if ((c2 = CheckTagField(c, cbuf.len,
							"modeminit="))) {
							memcpy(pcinfo.modeminit, c2, cbuf.len - 10);
							pcinfo.modeminit[cbuf.len-10] = 0;
						} else if ((c2 = CheckTagField(c, cbuf.len,
							"modemdev="))) {
							memcpy(pcinfo.modemdev, c2, cbuf.len - 9);
							pcinfo.modemdev[cbuf.len-9] = 0;
						} else if ((c2 = CheckTagField(c, cbuf.len,
							"modemdial="))) {
							memcpy(pcinfo.modemdial, c2, cbuf.len - 10);
							pcinfo.modemdial[cbuf.len-10] = 0;
						} else if ((c2 = CheckTagField(c, cbuf.len,
							"password="))) {
							memcpy(pcinfo.password, c2, cbuf.len - 9);
							pcinfo.password[cbuf.len-9] = 0;
						} else if ((c2 = CheckTagField(c, cbuf.len,
							"answertime="))) {
							memcpy(tmpstr, c2, cbuf.len - 11);
							tmpstr[cbuf.len-11] = 0;
							pcinfo.answertime = atoi(tmpstr);
						} else if ((c2 = CheckTagField(c, cbuf.len,
							"msgretries="))) {
							memcpy(tmpstr, c2, cbuf.len - 11);
							tmpstr[cbuf.len-11] = 0;
							pcinfo.msgretries = atoi(tmpstr);
						} else if ((c2 = CheckTagField(c, cbuf.len,
							"maxmsgsize="))) {
							memcpy(tmpstr, c2, cbuf.len - 11);
							tmpstr[cbuf.len-11] = 0;
							pcinfo.maxmsgsize = atoi(tmpstr);
						} else if ((c2 = CheckTagField(c, cbuf.len,
							"dialer="))) {
							memcpy(tmpstr, c2, cbuf.len - 7);
							tmpstr[cbuf.len-7] = 0;
							if (!strcasecmp(tmpstr, "internal"))
								pcinfo.dialer = DIALER_INTERNAL;
							else if (!strcasecmp(tmpstr, "external"))
								pcinfo.dialer = DIALER_EXTERNAL;
							else {
								cferr = 1;
								break;
							}
						} else if ((c2 = CheckTagField(c, cbuf.len,
							"speed="))) {
							memcpy(tmpstr, c2, cbuf.len - 6);
							tmpstr[cbuf.len-6] = 0;
							if (!strcmp(tmpstr, "B110"))
								pcinfo.speed = B110;
							else if (!strcmp(tmpstr, "300"))
								pcinfo.speed = B300;
							else if (!strcmp(tmpstr, "1200"))
								pcinfo.speed = B1200;
							else if (!strcmp(tmpstr, "2400"))
								pcinfo.speed = B2400;
							else if (!strcmp(tmpstr, "4800"))
								pcinfo.speed = B4800;
							else if (!strcmp(tmpstr, "9600"))
								pcinfo.speed = B9600;
							else if (!strcmp(tmpstr, "19200"))
								pcinfo.speed = B19200;
							else if (!strcmp(tmpstr, "38400"))
								pcinfo.speed = B38400;
							else {
								cferr = 1;
								break;
							}
						} else if ((c2 = CheckTagField(c, cbuf.len,
							"parity="))) {
							memcpy(tmpstr, c2, cbuf.len - 7);
							tmpstr[cbuf.len-7] = 0;
							if (!strcasecmp(tmpstr, "none"))
								pcinfo.parity = 0;
							else if (!strcasecmp(tmpstr, "even"))
								pcinfo.parity = PARENB;
							else if (!strcasecmp(tmpstr, "odd"))
								pcinfo.parity = PARENB|PARODD;
							else {
								cferr = 1;
								break;
							}
						} else if ((c2 = CheckTagField(c, cbuf.len,
							"databits="))) {
							memcpy(tmpstr, c2, cbuf.len - 9);
							tmpstr[cbuf.len-9] = 0;
							if (!strcmp(tmpstr, "8"))
								pcinfo.databits = CS8;
							else if (!strcmp(tmpstr, "7"))
								pcinfo.databits = CS7;
							else if (!strcmp(tmpstr, "6"))
								pcinfo.databits = CS6;
							else if (!strcmp(tmpstr, "5"))
								pcinfo.databits = CS5;
							else {
								cferr = 1;
								break;
							}
						} else if ((c2 = CheckTagField(c, cbuf.len,
							"protocol="))) {
							memcpy(tmpstr, c2, cbuf.len - 9);
							tmpstr[cbuf.len-9] = 0;
							if (!strcasecmp(tmpstr, "pet-pg1"))
								pcinfo.protocol = PC_PET1;
							else if (!strcasecmp(tmpstr, "pet-pg3"))
								pcinfo.protocol = PC_PET3;
							else if (!strcasecmp(tmpstr, "null"))
								pcinfo.protocol = PC_NULL;
							else {
								cferr = 1;
								break;
							}
						} else if ((c2 = CheckTagField(c, cbuf.len,
							"stopbits="))) {
							memcpy(tmpstr, c2, cbuf.len - 9);
							tmpstr[cbuf.len-9] = 0;
							if (!strcmp(tmpstr, "1"))
								pcinfo.stopbits = 0;
							else if (!strcmp(tmpstr, "2"))
								pcinfo.stopbits = CSTOPB;
							else {
								cferr = 1;
								break;
							}
						} else {
							cferr = 1;
							break;
						}

						break;

				} /* switch where */

				break;

			case CF_ALIAS:

				switch (where) {

					case 1:		/* alias name -- key */
						if (!cbuf.len) {
							cferr = 1;
							break;
						}
						keystring.bufused = 0; /* start new key */
						datastring.bufused = 0; /* start new data too */
						if (AddToDynString(&keystring, cbuf.buf+cbuf.start,
								cbuf.len)) {
							cferr = 1;
							break;
						}
						break;

					default:	/* alias values -- data */
						if (cbuf.len) {
							if (AddToDynString(&datastring, cbuf.buf+cbuf.start,
									cbuf.len)) {
								cferr = 1;
							}
						} else {
							/* only accept blank line if value defined */
							if (!datastring.bufused) {
								cferr = 1;
							} else {
								/* reset for next line */
								type = 0; 

								++naliases;

								/* add null byte to signal last value */
								if (AddToDynString(&datastring, &nullbyte, 0)) {
									cferr = 1;
									break;
								}

								dbkey.data = keystring.buf;
								/* don't store null terminator on key */
								dbkey.size = keystring.bufused -1;
								dbval.data = datastring.buf;
								dbval.size = datastring.bufused;
								if (taliasdb->put(taliasdb, &dbkey, &dbval,
									(u_int)R_NOOVERWRITE)) {
									report(LOG_ERR,
										"db->put() failed - line %d, errno=%d",
										lineno, errno);
									cferr = 1;
									break;
								} /* if db->put */
							} /* else */
						} /* else */
						break;

				} /* switch here */

				break;

			default:
				report (LOG_ERR, "internal error ReadConfig() switch type");
				break;

		} /* switch type */

		if (cferr) {
			report (LOG_ERR, "%s: syntax error line %d", fname, lineno);
			break;
		}

		/* exit out of this loop on eof */
		if (r == 1)
			break;

	} /* while 1 */

	err = cferr;

	if (!err) {
		report (LOG_INFO, "%.512s: read %d aliases, %d pet", fname, naliases,
			npets);

		/* juggle the database pointers */

		/* close the old */
		if (*aliasdb)
			err |= (*aliasdb)->close(*aliasdb);
		if (*pcdb)
			err |= (*pcdb)->close(*pcdb);  

		/* swap in the new datbase pointers */
		*aliasdb = taliasdb;
		*pcdb = tpcdb; 

		/* swap in the new ncentral */
		*ncentral = tncentral;


	} 
		

ReadConfigout:

	if (err) {
		if (taliasdb)
			err |= taliasdb->close(taliasdb);
		if (tpcdb)
			err |= tpcdb->close(tpcdb);

	} /* err */

	if (fd != -1)
		err |= close(fd);

	if (datastring.buf)
		free (datastring.buf);

	if (keystring.buf)
		free (keystring.buf);

	return err;  

} /* ReadConfig */

/*********************************************************************/
/* Function: GetNextField
/*
/*	returns next field (non whitespace separated by whitespace, 
/*
/* Returns: 0	good	lineno reflects the current line in the input.
/*						cbuf.buf+cbuf.start points to the start of the
/*						field, cbuf.len is the length of the field, and
/*						cbuf.end points to the end cbuf.eof and cbuf.bufused
/*						are used internally.
/*			-1	error	
/*			1	got eol with blank field.  lineno updated.
/*
/*			cbuf should be bzero()'d for the initial call.
/*
/*			fields need to be less that CBUF_BYTES chars long, or
/*			will return error condition.
/*
/*			fields with the first non white space being # are returned
/*			as blank lines (buffer is flushed till eoln)
/*
/*			\ can be used to quote characters.  Quoting \n ignores it.
/*
/*			" can be used to allow spaces to be returned in a field.  The
/*			trailing " is not required.  ie "This is a test" or
/*			This" "is" "a" "test are returned the same.
/*
/*			no error reporting is done.
/*
/*********************************************************************/
GetNextField(fd, cbuf, lineno)
int fd;
struct cbuf *cbuf;
int *lineno;
{

	int done, n, eof, x, eol, comment, start,  escaped, inquote, len;
	int end, incomment, starto, startb, need_read;
	unsigned char c;

	end = 0;		/* not done */
	eol = 0;		/* not eol */
	start = 0;		/* got start */
	len = 0;		/* length of field */
	comment = 0;	/* in comment? */
	escaped = 0;	/* got the escape char */
	incomment = 0;	/* in comment */
	starto = 0;		/* start offset */
	startb = 0;		/* compare ofset */
	need_read = 0;	/* need to do a read()? */
	eof = cbuf->eof;/* remember from last time */
	inquote = 0;	/* in " " */

	/* move unused portion of buffer (left over from previous call)
		to index 0 of the buffer.  end = start = 0 == no field found
		in buffer */

	if (cbuf->end) {
		bcopy(cbuf->buf+cbuf->end, cbuf->buf, cbuf->bufused - cbuf->end);
		cbuf->bufused -= cbuf->end;
		cbuf->start = cbuf->end = 0;
	} else
		cbuf->start = cbuf->bufused = 0;
	
	while (1) {

		/* only perform a read() if needed */
		if (((need_read) || (!cbuf->bufused)) && (!eof)) {

			if ((n = read(fd, cbuf->buf+cbuf->bufused, CBUF_BYTES - 
				cbuf->bufused)) == -1) 
				return -1; /* bad */

			/* set EOF condition (don't try to read anymore) if bytes
				requested is != bytes returned */

			if (n != (CBUF_BYTES - cbuf->bufused))
				eof = 1;
		} else
			n = 0;

		/* at this point, n is bytes read */

		cbuf->bufused += n;

		for (x = startb; x < cbuf->bufused; ++x) {


			c = cbuf->buf[x];


			/*
			/* # character before any non white space marks comment lines
			*/

			if ((!escaped) && (!start) && (!inquote) && (c == '#')) {
				incomment = 1;
				continue;
			}

			/*
			/* ignore everything to eol when in comment 
			/* return it like it's just a eol
			*/

			if (incomment) {
				if (c == '\n')  {

					incomment = 0; 

					eol = 1;

					/*
					/* end must be set so to be able to preserve anything
					/* that's left after the eol.  If nothing has been
					/* read yet, then there's nothing to preserve
					*/

					if ((x+1) < cbuf->bufused)
						end = x + 1;

					break;

				}

				continue;
			}

			/*
			/* process escaped -- \? characters
			*/

			if (escaped) {

				escaped = 0; /* not in escape mode anymore */

				/* an escaped \n means just ignore it */
				if (c == '\n') {

					/* if there's anything left after the \n char, copy it */
					if (x < cbuf->bufused)
						bcopy(cbuf->buf+x+1, cbuf->buf+x, cbuf->bufused - x);

					/* there's 1 less character in the buffer */
					cbuf->bufused -= 1;
					x -= 1;

					continue;
				}

				/* anything else escaped is taken as is.  ie. \u == u */

				/* count this in the length of the field */
				++len;
			
				continue;
			}

			/*
			/* test for and remove escape character
			*/

			if (c == '\\') {

				escaped = 1; /* set flag */

				/* if there's anything left after the \ char, copy it over */
				if (x < cbuf->bufused)
					bcopy(cbuf->buf+x+1, cbuf->buf+x, cbuf->bufused - x);

				/* there's 1 less character in the buffer */
				cbuf->bufused -= 1;
				x -= 1;

				continue;

			}

			/*
			/* test for and remove quote character
			*/

			if (c == '"') {

				inquote = (inquote)?0:1;

				/* if there's anything left after the \ char, copy it over */
				if (x < cbuf->bufused)
					bcopy(cbuf->buf+x+1, cbuf->buf+x, cbuf->bufused - x);

				/* there's 1 less character in the buffer */
				cbuf->bufused -= 1;
				x -= 1;

				continue;
			}
			

			/*
			/* test for eol
			*/

			if (c == '\n') {

				eol = 1;

				/* if in a field, this is the last char */
				/* or where to overwrite next call */
				if (start)
					end = x;

				/* else the last char is the next one */
				else if ((x+1) < cbuf->bufused)
					end = x + 1;
			
				break;

			}

			/*
			/* skip whitespace, but it also terminates a field
			/* consider null whitespace
			*/

			if ((!inquote) && ((c == ' ') || (c == '\t') || (!c))) {
				if (start) {
					end = x;
					break;
				}
				continue;
			}

			if (!start) {
				start = 1;
				starto = x;
			}

			/* increment length of field var */
			++len;

		} /* for x */

	/* a field has been successfully read if either
		1) eol was hit (maybe be an empty field)
		2) eof was hit (maybe be an empty field)
		3) a field started, and ended */


	/* check for field overflow condition */
	if (len+1 > CBUF_BYTES)
		return -1;

	/* increment line counter */
	if (eol)
		++*lineno;

	if (eol || end || eof)
		break;

	/* at this point, the field has not ended for any reason, and
		eof has not been hit.  Need to request more data */
	need_read = 1;

	/* if there is something to preserve, then do it */
	if (starto) 
		bcopy(cbuf->buf+starto, cbuf->buf, len);

	cbuf->bufused = len;
	startb = len;
	starto = 0;

	} /* while 1 */

	cbuf->start = starto;
	cbuf->end = end;
	cbuf->len = len;
	cbuf->eof = eof;

	if ((!end) && eof)
		return 1;

	return 0; /* good */

} /* GetNextField */

/*********************************************************************/
/* Function: AddToDynString
/*	adds a string (p) of length (len) to the dynamic string (str)
/*	then null terminates it.
/*
/* Returns: 0	good	the string was added
/*			!0	bad
/*
/*	a dynstring is just an array of bytes that can grow dynamically.
/*
/*	The initial call should use a bzero()'d str
/*	The caller is responsible for free()ing the storage allocated.
/*
/*	errors are reported via report()
/*
/*********************************************************************/

AddToDynString(str, p, len)
struct dynstring *str;
char *p;
int len;
{

	char *tmpp;

	while (1) {
		/* allocate length + null byte bytes ?? */
		if ((len + 1 + str->bufused) > (str->nblocks * ADD_STR_BLOCKSIZE)) {
			/* allocate more */
			if (!str->nblocks) {
				if (!(str->buf = (char*) malloc((unsigned)(++str->nblocks *
					ADD_STR_BLOCKSIZE)))) {
					report (LOG_ERR, "AddToDynString() can't malloc()");
					return -1; /* bad */
				}
			} else {
				tmpp = str->buf;
				if (!(str->buf = (char*) realloc(str->buf, (unsigned)
					(++str->nblocks * ADD_STR_BLOCKSIZE)))) {
					str->buf = tmpp; /* realloc null'd it */
					report (LOG_ERR, "AddToDynString() can't realloc()");
					if (str->buf)
						free (str->buf);
					str->buf = (char*)NULL;
					return -1; /* bad */
				}
			} /* else */
		} else 
			break; /* done allocating */
	} /* while 1 */

	bcopy(p, str->buf+str->bufused, len);
	str->buf[str->bufused+len] = 0; /* null terminate it */
	str->bufused += len +1;

	return 0; /* good */
	
} /* AddToDynString */


/*********************************************************************/
/* Function: AliasExpand
/*	recursively expands an alias in key
/*
/* Returns: struct dystring.
/*			if retlist.bufused == -1, then error condition was found.
/*
/*	The caller is responsible for free()'ing the storage allocated
/*	to dynstring.
/*
/*	If the alias doesn't expand, the returned retlist has retlist.bufused
/*	set to 0, and no storage was allocated.
/*
/*	Will only recurse up to MAX_ALIAS_RECURSE_DEPTH before returning error.
/*
/*	The alias list is terminated with a null string.
/*
/*	The value passed in key may be munged.
/*
/*	aliases can either be
/*		xxx	yyy			: AliasExpand(xxx) returns "yyy"
/*		xxx	yyy	zzz		: AliasExpand(xxx) returns "yyy zzz"
/* 
/*		xxx	/etc/yyy	: AliasExpand(xxx) returns contents of file /etc/yyy 
/*		xxx	/etc/yyy:z	: AliasExpand(xxx) returns contents of file /etc/yyy, 
/*							but if file couldn't be read or parsed, return "z"
/*		xxx |/etc/yyy	: AliasExpand(xxx) returns output of program /etc/yyy
/*		xxx |/etc/yyy:z	: AliasExpand(xxx) returns output of program /etc/yyy,
/*							but if program couldn't be run or no output, 
/*							return "z"
/*
/*	errors are reported via report()
/*
/*********************************************************************/
struct dynstring AliasExpand(aliasdb, key)
DB *aliasdb;
DBT key;
{

	struct dynstring tmplist, retlist, tmplist2;
	DBT val, tmpkey;
	int ret, len;
	char *p, *q, nullbyte, *tmpd;
	extern int alias_recurse_depth;

	if (++alias_recurse_depth > MAX_ALIAS_RECURSE_DEPTH) {
		report (LOG_ERR, "ExpandAlias() nested too deep (%d)",
			alias_recurse_depth);
		retlist.bufused = -1; /* caller knows it failed */
		--alias_recurse_depth;
		return retlist;
	}

	bzero(&retlist, sizeof retlist);
	bzero(&tmplist, sizeof tmplist);
	bzero(&tmplist2, sizeof tmplist2);
	tmpd = (char*)NULL;
	nullbyte = 0;

	/* if this is the first level of recursion, skip the special file
		and pipe lookups (the data for these is expected to be null
		terminated anyways, which it's not from the first call)
	*/
	if (alias_recurse_depth == 1)
		goto skipspecial;


	p = key.data;

	/*
	/* if the alias begins with a /, then this is a file lookup 
	/* else, if it begins with a |, this is a program lookup
	*/

	if (*p == '/') {

		/*
		/* key.data is not null terminated, which is required to make
		/*	a pathname.  malloc() a copy.
		*/

		if (!(tmpd = (char*)malloc((int)key.size+1))) {
			report (LOG_ERR, "malloc %d failed", (int)key.size);
			retlist.bufused = -1; /* signal error to caller */
			return retlist;
		}

		bcopy((char*)key.data, (char*)tmpd, (int)key.size);
		p = tmpd;
		*(p+key.size) = 0; 

		/* see if there is a default (ends in :default) */
		for (q = p; *q && (*q != ':'); ++q);

		if (*q == ':') 
			*q++ = 0;	 /* separate file and defalias */
		else
			q = (char*)NULL;

		/* p == pathname, q == default alias */
		tmplist2 = ReadAliasByFile(p, q);

		/* tmp copy was only needed for above call */
		if (tmpd)
			free (tmpd);

		/* tmplist2 will either have something in it, or be an error --
			either way, it's right for the caller */

		/* if error, or no expansion return the list -- it won't expand 
			further */
		if ((!tmplist2.bufused) || (tmplist2.bufused == -1) ||
			(tmplist2.bufused == -2)) 
			return tmplist2;

		p = tmplist2.buf;
		goto listexp1;


	/* by file */

	} else if (*p == '|') { 


		/*
		/* key.data is not null terminated, which is required to make
		/*	a pathname.  malloc() a copy.
		*/

		if (!(tmpd = (char*)malloc((int)key.size+1))) {
			report (LOG_ERR, "malloc %d failed", (int)key.size);
			retlist.bufused = -1; /* signal error to caller */
			return retlist;
		}

		bcopy((char*)key.data, (char*)tmpd, (int)key.size);
		p = tmpd;
		*(p+key.size) = 0; 

		++p; /* don't pass the | */

		/* see if there is a default (ends in :default) */
		for (q = p; *q && (*q != ':'); ++q);

		if (*q == ':') 
			*q++ = 0;	 /* separate file and defalias */
		else
			q = (char*)NULL;

		/* p == pathname, q == default alias */
		tmplist2 = ReadAliasByProgram(p, q);

		/* tmp copy was only needed for above call */
		if (tmpd)
			free (tmpd);

		/* tmplist2 will either have something in it, or be an error --
			either way, it's right for the caller */

		/* if error, or no expansion return the list -- it won't expand 
			further */
		if ((!tmplist2.bufused) || (tmplist2.bufused == -1) ||
			(tmplist2.bufused == -2)) 
			return tmplist2;

		p = tmplist2.buf;
		goto listexp1;


	} /* by program */



skipspecial:

	/* try a database lookup, if this fails, return 0 byte list - ie
		the alias didn't expand */
	
	if ((ret = aliasdb->get(aliasdb, &key, &val, (u_int)0)) == -1) {
		report (LOG_ERR, "ExpandAlias() db->get failed, errno=%d", errno);
		retlist.bufused = -1; /* caller knows it failed */
		--alias_recurse_depth;
		return retlist;
	}

	/* lookup failed? */
	if (ret == 1) {
		--alias_recurse_depth;
		return retlist; /* nothing */
	}

	/* p points to the first entry in the list, each entry is separated
	by a null byte, with the last entry being a null string itself */
	p = val.data;

listexp1:

	/* foreach entry in the list, try to expand it. */
	while (1) {
		
		if (!(len = strlen(p))) /* list is terminated with null string */
			break;

		tmpkey.data = p;
		tmpkey.size = len;

		tmplist = AliasExpand(aliasdb, tmpkey);

		/*
		/* if it didn't expand, but there was a non fatal error then
		/*	return an empty list -- ie program alias fails
		*/
		if (tmplist.bufused == -2) {
			if (tmplist2.buf)
				free (tmplist2.buf);
			return retlist;
		/* if it didn't expand, add p to the return list */
		} else if (!tmplist.bufused) {
			if (AddToDynString(&retlist, p, len)) {
				retlist.bufused = -1; /* caller knows it failed */
				--alias_recurse_depth;
			}
		} else if (tmplist.bufused == -1) { /* call failed */
			retlist.bufused = -1;
			--alias_recurse_depth;
			if (tmplist2.buf);
				free (tmplist2.buf);
			return retlist; /* caller knows it failed, propogate up */
		} else { /* call returned a list, add all but the trailing null
					and the end of list marker (second null)  */
			if (AddToDynString(&retlist, tmplist.buf, tmplist.bufused-2)) {
				retlist.bufused = -1; /* caller knows it failed */
				--alias_recurse_depth;
				if (tmplist2.buf)
					free (tmplist2.buf);
				if (tmplist.buf)
					free (tmplist.buf);
				return retlist;
			} /* if */
		} /* else */

		/* on to the next entry in the list */
		p += len+1;

	} /* while 1 */

	/* caller needs to free the space allocated by AliasExpand */
	/* ...and we were a caller */
	if (tmplist.buf)
		free (tmplist.buf);
	if (tmplist2.buf)
		free (tmplist2.buf);
		
	/* add trailing null byte - end of list marker if anything expanded */
	if (retlist.bufused) 
		if (AddToDynString(&retlist, &nullbyte, 0)) {
			retlist.bufused = -1;
			--alias_recurse_depth;
			return retlist;
		}

	--alias_recurse_depth;
	return retlist;

} /* AliasExpand */

/*********************************************************************/
/* Function: ReadAliasByFile
/*	implements the file reading code for AliasExpand()
/*
/* Returns: struct dystring.
/*			if retlist.bufused == -1, then error condition was found.
/*
/*	The caller is responsible for free()'ing the storage allocated
/*	to dynstring.
/*
/*	if the file can't be read, or there are no valid alias fields in it,
/*	the retlist will contain the single element pointed to by defalias.
/*	if defalias is null, no storage will be allocated - retlist.bufused
/*	will be 0.
/*
/*	errors are reported via report()
/*********************************************************************/
struct dynstring ReadAliasByFile(fname, defalias)
char *fname, *defalias;
{
	struct dynstring retlist;
	struct cbuf cbuf;
	int fd, err, ret, lineno;
	char nullbyte;

	err = 1; /* bad */
	fd = -1; /* not opened */
	bzero(&retlist, sizeof retlist);
	bzero(&cbuf, sizeof cbuf);
	nullbyte = 0;

	/* open the file */
	if ((fd = open(fname, O_RDONLY, 0)) == -1) {
		report (LOG_ERR, "open (%.512s): %s", fname, strerror(errno));
		goto ReadAliasByFileout;
	}

	while (1) {

		if ((ret = GetNextField(fd, &cbuf, &lineno)) == -1) {
			report (LOG_ERR, "GetNextField() returned error condition");
			goto ReadAliasByFileout;
		}

		if (cbuf.len)
			if (AddToDynString(&retlist, cbuf.buf+cbuf.start, cbuf.len))
				goto ReadAliasByFileout;

		/* done on eof */
		if (ret == 1)
			break;
	}

	err = 0; /* good */

ReadAliasByFileout:

	if (err) {
		if (retlist.buf) {
			free (retlist.buf);
			retlist.buf = (char*)NULL;
		}
	}

	/* if error condition was set or empty file, try using the default */
	if ((err || (!retlist.bufused)) && defalias) {
		if (AddToDynString(&retlist, defalias, strlen(defalias))) {
			if (retlist.bufused) {
				free (retlist.buf);
				retlist.buf = (char*)NULL;
			}
			err = 1;
		} else {
			err = 0;
		}
	}

	/* add trailing null string */
	if (!err && retlist.bufused) 
		if (AddToDynString(&retlist, &nullbyte, 0)) {
			free (retlist.buf);
			retlist.buf = (char*)NULL;
			err = 1;
		}

	if (err)
		retlist.bufused = -2;

	if (fd != -1)
		close(fd);

	return retlist;

} /* ReadAliasByFile */

/*********************************************************************/
/* Function: CheckTagField
/*
/* Returns: pointer to data portion of tag (tag=data), or if tag
/*	doesn't match or length error (char*)0L;
/*			
/*********************************************************************/
char *CheckTagField(buf, len, tag)
char *buf, *tag;
int len;
{
	int n;

	n = strlen(tag);

	/* if the length of the buffer is less than the length of the
		tag field then can't possibly match */

	if (len < n)
		return (char*)0L;

	/* check for a match */
	if (strncasecmp(buf, tag, n))
		return (char*)0L;

	/* got match, make sure length of data is okay */

	if ((len - n + 1) > PCINFO_STR_LEN)
		return (char*)0L;

	return buf+n;

} /* CheckTagField */

/*********************************************************************/
/* Function: ReadAliasByProgram
/*	implements the program/pipe reading code for AliasExpand()
/*
/* Returns: struct dystring.
/*			if retlist.bufused == -1, then error condition was found.
/*
/*	The caller is responsible for free()'ing the storage allocated
/*	to dynstring.
/*
/*	If the program can't be opened, or there is no output, or
/*	there is no output in PIPE_READ_SECONDS then the default
/*	alias (if non null) is returned.
/*
/*	the program argument may be munged.
/*
/*	errors are reported via report()
/*********************************************************************/
struct dynstring ReadAliasByProgram(program, defalias)
char *program, *defalias;
{
	struct dynstring retlist;
	struct cbuf cbuf;
	int err, ret, lineno;
	char nullbyte;
	int pipefd[2], statloc;
	pid_t pid;
	char *pgm, *arg, *p;
	extern char *sendpage_env[];

	err = 1; /* bad */
	bzero(&retlist, sizeof retlist);
	bzero(&cbuf, sizeof cbuf);
	nullbyte = 0;
	pipefd[0] = pipefd[1] = -1;

	arg = (char*)0L;

	/*
	/* the program string can also contain a single argument
	*/

	for (p = program; *p; ++p)
		if (*p == ' ') 
			*p = 0, arg = p+1;

	/* program name is the last path element */
	if ((pgm = strrchr(program, '/')))
		++pgm;
	else
		pgm = program;

	if (pipe(pipefd) == -1) {
		report (LOG_ERR, "ReadAliasByProgram: pipe(): %s", strerror(errno));
		goto ReadAliasByProgramout;
	}

	if ((pid = fork()) == -1) {
		report (LOG_ERR, "ReadAliasByProgram: fork(): %s", strerror(errno));
		goto ReadAliasByProgramout;
	}

	if (pid) { /* parent */

		/* close write end */
		if (close (pipefd[1])) {
			report (LOG_ERR, "ReadAliasByProgram: close(): %s",
				strerror(errno));
			goto skip1;
		}

		pipefd[1] = -1;

		while (1) {

			if ((ret = GetNextField(pipefd[0], &cbuf, &lineno)) == -1) {
				report (LOG_ERR, "GetNextField() returned error condition");
				goto skip1;
			}

			if (cbuf.len)
				if (AddToDynString(&retlist, cbuf.buf+cbuf.start, cbuf.len))
					goto skip1;

			/* done on eof */
			if (ret == 1)
				break;
		}

		err = 0; /* good */

skip1:
		/* no zombies */
		statloc = 0;
		if (waitpid(pid, &statloc, 0) == -1)
			report (LOG_ERR, "waitpid(): %s", strerror(errno));

		/* expect exit status 0 */
		if (statloc) {
			report (LOG_WARNING,
				"%.512s exited with status %d", program, statloc);
			err = 1;
			goto ReadAliasByProgramout;
		}


	} /* parent */ else {

		/* child */

		if (close(pipefd[0])) {
			report (LOG_ERR, "child: pipe read close(): %s", strerror(errno));
			exit (1);
		}

		/* pipe becomes stdout */
		if (dup2(pipefd[1], STDOUT_FILENO) != STDOUT_FILENO) {
			report (LOG_ERR, "child: dup2() to stdout failed");
			exit (1);
		}

		if (close (pipefd[1])) {
			report (LOG_ERR, "child: pipe write close(): %s", strerror(errno));
			exit (1);
		}

		/* invoke external program */
		if (arg)
			ret = execle(program, pgm, arg, (char*)0L, sendpage_env);
		else
			ret = execle(program, pgm, (char*)0L, sendpage_env); 
		if (ret == -1) {
			report (LOG_ERR,
				"child: execl() %.512s: %s", program, strerror(errno));
			exit (1);
		}

	} /* child */


ReadAliasByProgramout:


	if (err) {
		if (retlist.buf) {
			free (retlist.buf);
			retlist.buf = (char*)NULL;
		}
	}

	/* if error condition was set or empty file, try using the default */
	if ((err || (!retlist.bufused)) && defalias) {
		if (AddToDynString(&retlist, defalias, strlen(defalias))) {
			if (retlist.bufused) {
				free (retlist.buf);
				retlist.buf = (char*)NULL;
			}
			err = 1;
		} else {
			err = 0;
		}
	}

	/* add trailing null string */
	if (!err && retlist.bufused) 
		if (AddToDynString(&retlist, &nullbyte, 0)) {
			free (retlist.buf);
			retlist.buf = (char*)NULL;
			err = 1;
		}

	if (err)
		retlist.bufused = -2;

	if (pipefd[0] != -1)
		err |= close(pipefd[0]);

	if (pipefd[1] != -1)
		err |= close(pipefd[1]);

	return retlist;

} /* ReadAliasByProgram */
