/***************************************************************************/
/*  Module:      $Id: queue.c,v 1.8 1995/08/28 02:35:47 maf Exp $
/*  Description: queueing and support functions for sendpage
/*  Author:      maf
/* Copyright (c) 1995 Mark Fullmer and The Ohio State University
/***************************************************************************/

/*
$Log: queue.c,v $
 * Revision 1.8  1995/08/28  02:35:47  maf
 * *** empty log message ***
 *
 * Revision 1.7  1995/08/28  02:00:07  maf
 * stdio bug, comments, lastEntry bug
 *
 * Revision 1.6  1995/05/24  02:08:57  maf
 * fixed previous queue file bug again (or maybe for real!)
 *
 * Revision 1.5  1995/05/24  00:51:43  maf
 * all unknown string sizes use %.512s for report()
 * fixed race condition in ExpandQueue()
 *  - entries could get marked for processing twice, but they still would
 *  - only get done once since the queue file was removed, which caused
 *  - a strange warning message.
 *
 * Revision 1.4  1995/03/17  04:34:35  maf
 * deferstring not initialized correctly.
 *
 * Revision 1.3  1995/03/17  04:13:14  maf
 * entry->len not set correctly in AddMessagegeQueue()
 * abandon packet == page rejected, not retry later
 * fixed logic to send replies
 *
 * Revision 1.2  1995/03/15  04:40:55  maf
 * *** empty log message ***
 *
 * Revision 1.1  1995/01/10  01:43:50  maf
 * Initial revision
 *
*/


#include <sys/types.h>
#include <sys/time.h>
#include <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 <syslog.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <db.h>
#include <sys/uio.h>
#include "report.h"
#include "sendpage.h"

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


/*********************************************************************/
/* Function: AddMessageQueue
/*	add a message to the disk based queue
/*
/*	Returns:	0	good
/*				!0	bad
/*
/* args:
/*		entry		pointer to struct messagequeue
/*					caller should initialize set all entries to appropiate vals
/*					ie senderlen to strlen(sender), queuetime to current time
/*					etc.
/*		sender		pointer to sender
/*		recipient	pointer to recipient (
/*		recipient2	pointer to recipient2 (this is recipient that has gone
/*					through alias expansion)
/*		message		pager message
/*		qfname2		set by function to file name of queue message.
/*
/*	The queue is written with a single wrivtev to prevent race conditions
/*		(ie client is writing queue wile daemon is processing it)
/*		0 length queue files are just skipped by the daemon.
/*
/*	errors are reported via report()
/*
/*********************************************************************/
AddMessageQueue(entry, sender, recipient, recipient2, message, qfname2)
struct messagequeue *entry;
char *sender, *recipient, *recipient2, *message;
char **qfname2;
{

	extern int errno, debug;
	int fd;
	int err;
	struct iovec vec[9];
	char nullbyte;
	/* 6 bytes for XXXXXX 10 bytes for max size of unsigned long in ascii */
	/* note sizeof also returns the null byte so the trailing 0 is included */
	static char qfname[sizeof QUEUE_HEAD + 16];

	fd = -1;
	err = 1; /* bad */
	nullbyte = 0;
	*qfname2 = (char*)qfname;

	/* make sure entry->*len are set correctly */
	entry->recipientlen = strlen(recipient);
	entry->recipient2len = strlen(recipient2);
	entry->senderlen = strlen(sender);
	entry->messagelen = strlen(message);

	/* construct prototype temp file */
	sprintf(qfname, "%s%luXXXXXX", QUEUE_HEAD, entry->queuetime);

	/* create and open the file */
	if ((fd = mkstemp(qfname)) == -1) {
		report (LOG_ERR, "mkstemp(%s%luXXXXXX): failed", QUEUE_HEAD,
			entry->queuetime);
		goto AddMessageQueueout;
	}

	/* write out the queue entry.  This needs to be atomic otherwise there 
		is a race condition with the daemon reading part of the file */
	/* the string fields are also null terminated here */
	vec[0].iov_base = (caddr_t)entry;
	vec[0].iov_len = sizeof(struct messagequeue);

	vec[1].iov_base = (caddr_t)recipient;
	vec[1].iov_len = entry->recipientlen;
	vec[2].iov_base = (caddr_t)&nullbyte;
	vec[2].iov_len = 1;

	vec[3].iov_base = (caddr_t)recipient2;
	vec[3].iov_len = entry->recipient2len;
	vec[4].iov_base = (caddr_t)&nullbyte;
	vec[4].iov_len = 1;

	vec[5].iov_base = (caddr_t)sender;
	vec[5].iov_len = entry->senderlen;
	vec[6].iov_base = (caddr_t)&nullbyte;
	vec[6].iov_len = 1;

	vec[7].iov_base = (caddr_t)message;
	vec[7].iov_len = entry->messagelen;
	vec[8].iov_base = (caddr_t)&nullbyte;
	vec[8].iov_len = 1;

	if (writev (fd, vec, 9) == -1) {
		report (LOG_ERR, "writev(): %s", strerror(errno));
		goto AddMessageQueueout;
	}

	err = 0; /* good */

	report (LOG_INFO,
		"%.512s: from=%.512s, size=%d, to=%.512s, status=%x, msg=%.512s",
		qfname, sender, entry->messagelen, recipient, entry->status, message);

AddMessageQueueout:

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

} /* AddMessageQueue */

/*********************************************************************/
/* Function: PrintMessageQueue
/*	print the contents of the message queue 
/*
/*	Returns	0	good
/*			!0	bad
/*
/*	The disk based message queue is printed to stdout.  If a message is
/*	being delivered, a * precedes it
/*
/*	errors are reported to stderr
/*
/*********************************************************************/
PrintMessageQueue()
{
	extern int errno, debug;
	char *pathname;
	int err, fd, n;
	struct dirent *dirent;
	DIR *dirp;
	int entries;
	struct stat statbuf;
	struct messagequeue *entry;
	char *queuebuf;
	int queuebufsize;
	struct tm *ptm, tm;
	char *sender, *recipient, *recipient2;
	char *tmpp;
	FILE *FP;
	unsigned int pid;

	err = 1; /* bad */
	entries = 0;
	dirp = (DIR*)NULL;
	fd = -1; /* bad */
	queuebuf = (char*)NULL;
	pid = 0;

	/* alert the user of the daemon is not running */
	if ((FP = fopen(PATH_SENDPAGE_PIDFILE, "r"))) {

		fscanf(FP, "%u", &pid);

		if (kill ((int)pid, SIGUSR1))
			fprintf(stderr, "warning, daemon does not appear to be running.\n");
		fclose(FP);
	} else {
		/* no pidfile to read... */
		fprintf(stderr,
			"warning, can't read daemon pidfile %s\n", PATH_SENDPAGE_PIDFILE);
	}
	

	/* allocate memory for contents of queue'd entry (may be expanded later) */
	if (!(queuebuf = (char*)malloc((queuebufsize = 1024)))) {
		fprintf(stderr, "malloc(1024) failed\n");
		goto PrintMessageQueueout;
	}

	/* allready did a chdir to PATH_QUEUE... */
	if (!(dirp = opendir("."))) {
		fprintf(stderr, "Can't opendir() %s\n", PATH_QUEUE);
		goto PrintMessageQueueout;
	}

	for (dirent = readdir(dirp); dirent; dirent = readdir(dirp)) {

		/* skip . and .. */
		if (dirent->d_name[0] == '.')
			if (!dirent->d_name[1])
				continue;
			if (dirent->d_name[1] == '.')
				if (!dirent->d_name[2])
					continue;

		/* skip anything that doesn't begin with QUEUE_HEAD */
		if (memcmp(dirent->d_name, QUEUE_HEAD, sizeof (QUEUE_HEAD)-1))
			continue;

		++entries;

		/* ignore open errors - there's a race condition that means
			an open error here is probably because the daemon deleted
			it
		*/
		if ((fd = open(dirent->d_name, O_RDONLY, 0)) == -1) {
			continue;
		}

		/* it's opened, so if was deleted, its really not */
		if (fstat(fd, &statbuf) == -1) {
			perror(dirent->d_name);
			goto PrintMessageQueueout;
		}

		/* sanity check thing could overflow below if  */
		if (statbuf.st_size > 32767)
			goto PrintMessageQueueout;

		/* make sure there's enough buffer space available to read it in */
		if (statbuf.st_size > queuebufsize) {

			queuebufsize += statbuf.st_size;

			tmpp = queuebuf;
			if (!(queuebuf = (char*)realloc(queuebuf, queuebufsize))) {
				queuebuf = tmpp;
				fprintf(stderr, "realloc(%d) failed\n", queuebufsize);
				goto PrintMessageQueueout;
			}
		}

		if ((n = read(fd, queuebuf, (int)statbuf.st_size)) == -1) {
			perror(dirent->d_name);
			goto PrintMessageQueueout;
		}

		if (close(fd))
			perror(dirent->d_name);
		fd = -1;

		if ((n != statbuf.st_size) || (n < sizeof (struct messagequeue))) {
			fprintf(stderr, "short queue file -- %s, skipping\n",
			dirent->d_name);
			continue;
		}

		/* struct comes first */
		entry = (struct messagequeue*)queuebuf;
		recipient = queuebuf+sizeof(struct messagequeue);
		recipient2 = recipient + entry->recipientlen + 1;
		sender = recipient2 + entry->recipient2len + 1;

		/* make sure this looks valid */
		n = sizeof(struct messagequeue) + entry->recipientlen +
			entry->recipient2len + entry->senderlen + entry->messagelen + 4;
		if (n != statbuf.st_size) {
			fprintf(stderr, "corrupt queue file -- %s, skipping\n",
				dirent->d_name);
				continue;
		}

		if (entries == 1)  {
			printf("Sendpage queue entries\n----------------------\n\n");

			printf ("#/Retries From                     To                       Size      When\n");
			printf ("--------------------------------------------------------------------------------\n");
		} /* if */

		if (!(ptm = localtime(&(entry->queuetime)))) {
			fprintf(stderr, "locatime() failed\n");
			goto PrintMessageQueueout;
		} else
			bcopy(ptm, &tm, sizeof(tm));

		printf("%c%-3d/%3d  %-24.24s %-24.24s %-4d %d/%d %d:%d:%d\n",
			entry->status&STATUS_DELIVERING ? (char)'*' : (char)' ', entries,
			entry->retries, sender, recipient, entry->messagelen,
			tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);

	} /* for dirent */

	if (!entries)
		printf("Pager queue is empty\n");


PrintMessageQueueout:

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

	if (dirp)
		err |= closedir(dirp);

	if (queuebuf)
		free (queuebuf);

	return err;

} /* PrintMessageQueue */

/*********************************************************************/
/* Function: QueueExpand
/*	Alias expands queue'd messages, and requeue's them so each queue
/*	entry has a single recipient (aliases can expand to multiple recipients)
/*
/*	Returns	0	good
/*			!0	bad
/*
/*	QueueExpand goes through the disk based message queue, and tried to
/*	do an alias expansion on each recipient.  If the alias expansion
/*	doesn't result in anything, the message is requeue'd with recipient2
/*	equal to recipient, and the STATUS_EXPANDED bit set in the status var.
/*
/*	If the alias does expand, each entry in the list of the alias expansion
/*	is queue'd as the original, but with recipient 2 is set to the list entry
/*	and the STATUS_EXPANDED bit set.
/*
/*	If the STATUS_EXPANDED bit is set, QueueExpand doesn't touch the entry.
/*
/*	Furthermore, dlist is (which is allocated and free()'d by the caller)
/*	will point to a list of queue entries for each paging central type.
/*	This effectively sorts the queue so multiple messages to the same
/*	paging central can be delivered in the same phone call.  The PET
/*	protocol supports this, maybe others won't...
/*
/*	errors are reported via report()
/*
/*********************************************************************/
QueueExpand(aliasdb, pcdb, ncentral, dlist)
DB *aliasdb, *pcdb;
u_int ncentral;
struct dynstring *dlist;
{

	extern int errno, debug;
	struct dynstring aliaslist;
	DBT dbkey, dbval;
	char *pathname;
	struct dirent *dirent;
	DIR *dirp;
	struct stat statbuf;
	struct messagequeue *entry, tmpentry;
	char *queuebuf;
	int queuebufsize;
	struct tm *tm;
	char *sender, *recipient, *recipient2, *message;
	int err, len, fd, n;
	char *p, *qfilename;
	char *qfname;
	char *tmpp;
	char nullbyte;
	struct dynstring dirstring;

	err = 1; /* bad */
	queuebuf = (char*)NULL;
	fd = -1;
	dirp = (DIR*)NULL;
	bzero(&aliaslist, sizeof(aliaslist));
	bzero(&dirstring, sizeof(dirstring));
	nullbyte = 0;
	

	/* The delivery list is an array of pointers to dynstrings.
		Each dynstring is a list of queue filenames corresponding to the
		paging central definition to use.

		index 0 contains the filenames that are invalid - the caller of this f
			will discard these, maybe notifying the sender

		index 1 is the first paging central definition in sendpage.cf
		index 2 is the second paging central definition in sendpage.cf

		The indexes are stored while reading in the config file, and are
		the first entry in the pcdb.data list

		caller sets up dlist

	*/

	/* allocate memory for contents of queue'd entry (may be expanded later) */
	if (!(queuebuf = (char*)malloc((queuebufsize = 1024)))) {
		report (LOG_ERR, "malloc(1024) failed\n");
		goto QueueExpandout;
	}

	if (!(dirp = opendir("."))) {
		fprintf(stderr, "Can't opendir() %s\n", PATH_QUEUE);
		goto QueueExpandout;
	}

	/*
	/* read the directory contents into a dynstring --
	/* since files will be added and removed from this directory
	/* its necessary to read it first, else there is a race
	/* condition.
	*/

	for (dirent = readdir(dirp); dirent; dirent = readdir(dirp)) {

		/* skip . and .. */
		if (dirent->d_name[0] == '.')
			if (!dirent->d_name[1])
				continue;
			if (dirent->d_name[1] == '.')
				if (!dirent->d_name[2])
					continue;

		/* skip anything that doesn't begin with QUEUE_HEAD */
		if (memcmp(dirent->d_name, QUEUE_HEAD, sizeof (QUEUE_HEAD)-1)) 
			continue;

		if (AddToDynString(&dirstring, dirent->d_name,
			strlen(dirent->d_name))) {
			report(LOG_ERR, "can't read directory into buf");
			goto QueueExpandout;
		}
	} /* for dirent */

	/* add trailing null byte */
	if (AddToDynString(&dirstring, &nullbyte, 0)) {
		report(LOG_ERR, "can't AddToDynstring nullbyte");
		goto QueueExpandout;
	}

	for (qfilename = dirstring.buf; *qfilename;
		qfilename += strlen(qfilename)+1) {
		
		if ((fd = open(qfilename, O_RDONLY, 0)) == -1) {
			report (LOG_ERR,
				"open(%.512s): %s", qfilename, strerror(errno));
			goto QueueExpandout;
		}

		/* it's opened, so if was deleted, its really not */
		if (fstat(fd, &statbuf) == -1) {
			report (LOG_ERR, "fstat(%.512s): %s",
				qfilename, strerror(errno));
			goto QueueExpandout;
		}

		/* sanity check thing could overflow below if  */
		if (statbuf.st_size > 32767)
			goto QueueExpandout;

		/* make sure there's enough buffer space available to read it in */
		if (statbuf.st_size > queuebufsize) {
			queuebufsize += statbuf.st_size;

			tmpp = queuebuf;
			if (!(queuebuf = (char*)realloc(queuebuf, queuebufsize))) {
				queuebuf = tmpp;
				report(LOG_ERR, "realloc(%d) failed\n", queuebufsize);
				goto QueueExpandout;
			}
		}

		if ((n = read(fd, queuebuf, (int)statbuf.st_size)) == -1) {
			report(LOG_ERR, "read(%.512s): %s",
				qfilename, strerror(errno));
			goto QueueExpandout;
		}

		if (close(fd))
			report(LOG_ERR, "close(%.512s): %s",
				pathname, strerror(errno));
		fd = -1;

		if ((n != statbuf.st_size) || (n < sizeof (struct messagequeue))) {
			report(LOG_WARNING, "short queue file -- %.512s, skipping\n",
				qfilename);
			continue;
		}

		/* struct comes first */
		entry = (struct messagequeue*)queuebuf;

		recipient = queuebuf+sizeof(struct messagequeue);
		recipient2 = recipient + entry->recipientlen + 1;
		sender = recipient2 + entry->recipient2len + 1;
		message = sender + entry->senderlen + 1;

		/* if this entry has allready been expanded, then skip it */
		if (entry->status & STATUS_EXPANDED) {

			if (AddToDelivery(dlist, pcdb, ncentral, qfilename,
				recipient2)) {
				report (LOG_ERR, "AddToDelivery() failed for %.512s",
					pathname);
				goto QueueExpandout;
			}

			continue;
		}


		dbkey.data = recipient;
		dbkey.size = entry->recipientlen;

		aliaslist = AliasExpand(aliasdb, dbkey);

		if (aliaslist.bufused == -1) {
			report (LOG_ERR, "AliasExpand() returned an error condition\n");
			goto QueueExpandout;
		}

		/* if no expansion, just requeue this entry with the STATUS_EXPANDED
			bit set and recipient2 equal to recipient */
		if (!aliaslist.bufused) {
			entry->status |= STATUS_EXPANDED;

			if (AddMessageQueue(entry, sender, recipient, recipient, message,
				&qfname)) {
				report (LOG_ERR, "addMessageQueue() failed\n");
				goto QueueExpandout;
			}

			if (AddToDelivery(dlist, pcdb, ncentral, qfname, recipient)) {
				report (LOG_ERR, "AddToDelivery() failed for %.512s", qfname);
				goto QueueExpandout;
			}

			/* and remove the old one */
			if (unlink(qfilename) == -1) {
				report (LOG_ERR, "unlink(%.512s): %s", qfilename,
					strerror(errno));
				goto QueueExpandout;
			}
		} else { /* alias expanded to list - need to requeue */

			tmpentry.queuetime = entry->queuetime;
			tmpentry.retries = 0;
			tmpentry.status = entry->status | STATUS_EXPANDED;
			tmpentry.senderlen = entry->senderlen;
			tmpentry.messagelen = entry->messagelen;
			tmpentry.recipientlen = entry->recipientlen;

			p = aliaslist.buf;

			for (; (len = strlen(p)); p += len+1) {

				tmpentry.recipient2len = len;

				if (AddMessageQueue(&tmpentry, sender, recipient, p, message,
					&qfname)) {
					report (LOG_ERR, "addMessageQueue() failed\n");
					goto QueueExpandout;
				} 

				if (AddToDelivery(dlist, pcdb, ncentral, qfname, p)) {
					report (LOG_ERR, "AddToDelivery() failed for %.512s",
						qfname);
					goto QueueExpandout;
				}

			} /* for p */

			/* and remove the old one */
			if (unlink(qfilename) == -1)  {
				report (LOG_ERR, "unlink(%.512s): %s", qfilename,
					strerror(errno));
				goto QueueExpandout;
			}
		} /* else */
			
	} /* for each in dirstring */

	/* mark the end of each delivery list */
	for (n = 0; n < ncentral + 1; ++n) 
		if (dlist[n].buf) 
			AddToDynString(&dlist[n], &nullbyte, 0);

	err = 0;

QueueExpandout:

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

	if (dirp)
		err |= closedir(dirp);

	if (queuebuf)
		free (queuebuf);

	if (aliaslist.buf)
		free (aliaslist.buf);

	if (dirstring.buf)
		free (dirstring.buf);

	return err;

} /* QueueExpand */

/*********************************************************************/
/* Function: AddToDelivery
/*	Adds a recipient to the delivery list 
/*
/*	Returns	0	good
/*			!0	bad
/*
/*	Takes a recipient that should of previously been alias expanded, 
/*	and determintes which delivery list to add it to.
/*	dlist[0] is the error list	CF_UNKNOWN == 0
/*	
/*	The delivery list is determined by looking in the PAGINGCENTRAL
/*	part of the recipient (ID.PAGINGCENTRAL).  If there is no PAGINGCENTRAL
/*	part, or the PAGINGCENTRAL part could not be looked up, the entry
/*	is added to the error list.
/*	If the PAGINGCENTRAL part was looked up, the appropiate delivery list
/*	is determined from the first string returned from the pcdb->get value.
/*
/*	dlist needs to be an array of ncentral+1 before calling, initially
/*	each entry in dlist needs to be bzero()'d.
/*
/*	AddToDynString() is called in here, so the elements of dlist need
/*	to be free()'d by the caller.
/*	
/*	reports errors via report()
/*********************************************************************/
AddToDelivery(dlist, pcdb, ncentral, fname, recipient)
struct dynstring *dlist;
u_int ncentral;
DB *pcdb;
char *fname, *recipient;
{

	char *pc;
	int lenfname;
	int err, ret;
	extern int debug;
	DBT val, key;
	int n;
	struct pcinfo pcinfo;

	err = 1; /* bad */

	/*
	The recipient is assumed to allready have gone through alias expansion 
	The PAGINGCENTRAL part should be in the pcdb database, else error.
	The recipient should be in the form ID.PAGINGCENTRAL ie 1111.usamobile1,
		else error.
	*/

	lenfname = strlen(fname);

	for (pc = recipient; *pc && (*pc != '.'); ++pc);

	/* if it's NULL (didn't find a .) store it to the error delivery list */
	if (!*pc) {

#ifdef DEBUG
	if (debug > 2)
		report (LOG_INFO, "Adding \"%.512s\" to error list (%.512s)", fname,
			recipient);
#endif /* DEBUG */
	
		if (AddToDynString(&dlist[0], fname, lenfname)) 
			goto AddToDeliveryout;

	} else { /* *pc == . */

		/* point pc to the PAGINGCENTRAL part */
		++pc;

		/* lookup the PAGINGCENTRAL part */
		key.data = pc;
		key.size = strlen(pc);

		if ((ret = pcdb->get(pcdb, &key, &val, (u_int)0)) == -1) {
			report (LOG_ERR, "AddToDelivery() db->get failed, errno=%d",
				errno);
			goto AddToDeliveryout;
		}

		/* lookup failed? */
		if (ret == 1) { /* yes, add it to the error delivery list */

#ifdef DEBUG
	if (debug > 2)
		report (LOG_INFO, "Adding \"%.512s\" to error list (%.512s)", fname,
			recipient);
#endif /* DEBUG */

			if (AddToDynString(&dlist[0], fname, lenfname)) 
				goto AddToDeliveryout;

		} else { /* no, add it to the delivery list */

			/* first string in the val is the index to the delivery list */
			memcpy(&pcinfo, val.data, val.size);
			n = pcinfo.id + 1;

#ifdef DEBUG
	if (debug > 2)
		report (LOG_INFO, "Adding \"%.512s\" to delivery list (%.512s %d)",
			fname, recipient, n);
#endif /* DEBUG */

			if (AddToDynString(&dlist[n], fname, lenfname)) 
				goto AddToDeliveryout;

		} /* else lookup failed */

	} /* else *pc == . */

	err = 0; /* good */	

AddToDeliveryout:

	return err;
} /* AddToDelivery */

/*********************************************************************/
/* Function: QueueRun
/*	Perform a delivery run on the queue
/*
/*	Returns	0	good
/*			!0	bad
/*
/*	if the queue needs to be run again (there are undelivered msgs)
/*	rerun is set to 1
/*
/* errors are reported via report()
/*
/*********************************************************************/
QueueRun(aliasdb, pcdb, ncentral, rerun)
DB *aliasdb, *pcdb;
u_int ncentral;
int *rerun;
{

	int err, n, fd, i, queuebufsize, mfd, x, senderr;
	extern int debug;
	struct dynstring *dlist; /* delivery list */
	char *queuebuf, *tmpp, *p, *q;
	char *sender, *recipient, *recipient2, *message;
	struct stat statbuf;
	struct messagequeue *entry;
	int pet_startup;
	DBT dbval, dbkey;
	char recipientid[255];
	struct cbuf cbuf;
	int listfail, pass, ret;
	struct pcinfo pcinfo;
	int didProtStartup, abandonList, sendReply, didProtShutdown;
	int lastEntry, qfAction;
	struct dynstring deferstring;
	char nullbyte;

	err = 1; /* bad */
	dlist = (struct dynstring*)NULL;
	*rerun = 0; /* don't need to run again */
	fd = -1; /* fd invalid */
	mfd = -1; /* mfd invalid */
	queuebuf = NULL;
	bzero(&cbuf, sizeof cbuf);
	bzero(&deferstring, sizeof deferstring);
	nullbyte = 0;

#ifdef DEBUG
	if (debug > 5)
		report (LOG_INFO, "begin QueueRun");
#endif /* DEBUG */

	/* allocate memory for delivery list */
	if (!(dlist = (struct dynstring*)malloc(sizeof (struct dynstring) *
		(ncentral + 1)))) {
		report (LOG_ERR, "malloc() for delivery list failed\n");
		*rerun = 1;
		goto skip2;
	}

	/* allocate memory for contents of queue'd entry (may be expanded later) */
	if (!(queuebuf = (char*)malloc((queuebufsize = 1024)))) {
		report (LOG_ERR, "malloc(1024) failed");
		*rerun = 1;
		goto skip2;
	}

	/* zero out the entries */
	for (n = 0; n < ncentral + 1; ++n)
		bzero(&dlist[n], sizeof(struct dynstring));


	/* expand and resolve aliases, construct a delivery list */
	if (QueueExpand(aliasdb, pcdb, ncentral, dlist)) {
		report (LOG_ERR, "QueueExpand() failed");
		*rerun = 1;
		goto QueueRunout;
	}

	/* for each delivery list */
	/* each entry contains a list of queue file names for a particular
	/* destination, with i==0 being the special error list */
	for (i = 0; i < (ncentral + 1); ++i) {

		/* if no entries for this destination, then skip */
		if (!dlist[i].buf)
			continue;

		didProtStartup = 0;	/* startup code called? */
		abandonList = 0;	/* give up on the entire list? */
		mfd = -1;			/* invalidate modem fd */


		/* for each entry in that list */
		/* ie. each queue file name */
		for (p = dlist[i].buf; *p; p += strlen(p)+1) {

			sendReply = 0;	/* force mail reply */
			fd = -1;		/* invalidate fd of queue file */
			lastEntry = 0;	/* this is Not the last entry for this pc */

#ifdef DEBUG
			if (debug > 5) 
				report(LOG_INFO, "i=%d, p=%.512s", i, p);
#endif /* DEBUG */

			/*
			/* if this is the last entry in the list, extra code
			/* needs to be executed to cleanup 
			*/
			if (!*(p + strlen(p)+1))
				lastEntry = 1;

			/* read in the queue file */
			if ((fd = open(p, O_RDWR, 0)) == -1) {
				report (LOG_WARNING, "Can't open queue file %.512s", p);
				*rerun = 1;
				goto skip2;
			}

			if (fstat(fd, &statbuf) == -1) {
				report (LOG_WARNING, "Can't fstat queue file %.512s", p);
				*rerun = 1;
				goto skip2;
			}

			/* sanity check */
			if (statbuf.st_size > 32767) {
				report (LOG_WARNING, "oversized queue file! -- %.512s", p);
				*rerun = 1;
				goto skip2;
			}

			/* make sure there's enough buffer space available to read it in */
			if (statbuf.st_size > queuebufsize) {

				queuebufsize += statbuf.st_size;

				tmpp = queuebuf;
				if (!(queuebuf = (char*)realloc(queuebuf, queuebufsize))) {
					queuebuf = tmpp;
					report (LOG_WARNING, "realloc(%d) failed\n", queuebufsize);
					*rerun = 1;
					goto skip2;
				}
			}

			entry = (struct messagequeue*)queuebuf;

			if ((n = read(fd, queuebuf, (int)statbuf.st_size)) == -1) {
				report (LOG_WARNING, "read(%.512s): ", p, strerror(errno));
				*rerun = 1;
				goto skip2;
			}

			/* increment retries */
			entry->retries ++;

			/* update the queue file for use with sendpage -bp */
			entry->status = STATUS_DELIVERING | STATUS_EXPANDED |
				(entry->status & STATUS_NOMAIL) |
				(entry->status & STATUS_MSGTRUNCATE) |
				(entry->status & STATUS_SIZETRUNCATE);

			/* unset other bits that could of been set previously */
			entry->status &= ~STATUS_TMPFAIL;


			if (UpdateQueueFile(p, fd, queuebuf, (int)statbuf.st_size)) {
				*rerun = 1;
				goto skip2;
			}

			/* calculate pointers to text fields */
			recipient = queuebuf+sizeof(struct messagequeue);
			recipient2 = recipient + entry->recipientlen + 1;
			sender = recipient2 + entry->recipient2len + 1;
			message = sender + entry->senderlen + 1;

			/*
			/* if this is the special error list, mark this as an
			/* error and skip processing any further -
			/* -- this must be done now, code further down assumes
			/* the recipient is formatted correctly and the paging
			/* central part exists
			*/
			if (i == 0) {

				entry->status = STATUS_NODELIVER1 |
					(entry->status & STATUS_NOMAIL);

				qfAction = QF_DELETE;
				sendReply = 1;
				goto skip1;
			}

			/* if the STATUS_MSGTRUNCATE bit was set, drop it now */
			if (entry->status & STATUS_MSGTRUNCATE) {

				qfAction = QF_DELETE;
				sendReply = 1;
				goto skip1;
			}

			/*
			*	get the "paging central" part so can lookup options
			*	for the destination 
			*/
			for (q = recipient2; *q != '.'; ++q);
			++q;
			dbkey.data = q;
			dbkey.size = strlen(q);

			if (pcdb->get(pcdb, &dbkey, &dbval, (u_int)0)) {
				report (LOG_ERR,
					"%.512s: unexpected pcdb->get failure", p);
				entry->status |= STATUS_NODELIVER2;
				qfAction = QF_UPDATE;
				sendReply = 1;
				*rerun = 1;
				goto skip1;
					
			}

			memcpy(&pcinfo, dbval.data, dbval.size);

			/* 
			/* if this pc has a max retries set, and the retries
			/* is greater than this number, drop it now 
			*/
			if ((pcinfo.msgretries) &&
				(entry->retries > pcinfo.msgretries )) {

					entry->status = STATUS_EXPIRED |
						(entry->status & STATUS_NOMAIL);

					qfAction = QF_DELETE;
					sendReply = 1;
					goto skip1;
			}
					

			/*
			/* if this pc has a max message size set and the 
			/* messagelen is greater than this, then truncate the
			/* message size, and set STATUS_SIZETRUNCATE
			*/

			if ((pcinfo.maxmsgsize) &&
				(entry->messagelen > pcinfo.maxmsgsize )) {

					statbuf.st_size -= (entry->messagelen - pcinfo.maxmsgsize);
					entry->messagelen = pcinfo.maxmsgsize;
					entry->status |= STATUS_SIZETRUNCATE;
					message[pcinfo.maxmsgsize] = 0;
				}

			/*
			/* get the "recipient" part for later use 
			*/
			tmpp = recipientid;
			x = 0;
			for (q = recipient2; *q != '.'; ++q, ++x) {
				if ((x+1) >= sizeof(recipientid))
					break;
				*tmpp++ = *q;
			}
			recipientid[x] = 0;


			/*
			/* call the appropriate protocol startup code 
			*/

			if (!didProtStartup)  {

				switch (pcinfo.protocol) {

					case PC_NULL:

						didProtStartup = 1;
					
						break;

					case PC_PET1:
					case PC_PET3:

						if (PetStartup(&pcinfo, &mfd, &cbuf, p))
							didProtStartup = 0;
						else
							didProtStartup = 1;

						break;
					default:

						report (LOG_ERR, "fatal, unknown protocol");
						entry->status = STATUS_NODELIVER2;
						qfAction = QF_DELETE;
						goto skip1;

						break;

					} /* switch */

			} /* need to call protocol startup */

			/*
			/* if the protocol startup code failed, then
			/* assume it will fail next time too -- 
			/* defer this entire list 
			*/

			if (!didProtStartup) {

				*rerun = 1;

				abandonList = 1;

				entry->status |= STATUS_TMPFAIL;

				qfAction = QF_UPDATE;
				goto skip1;

			}


			/*
			/* at this point, the protocol has started up, and is
			/* ready to process transactions
			*/
					

			switch (pcinfo.protocol) {

				case PC_NULL:

					entry->status = (entry->status & STATUS_NOMAIL) |
						(entry->status & STATUS_SIZETRUNCATE);

					entry->delivertime = time((time_t*)0L);
					qfAction = QF_UPDATE|QF_DEFER;
					goto skip1;

					break;

				case PC_PET1:
				case PC_PET3:

					if ((ret = SendPETTransaction(mfd, &cbuf, recipientid,
						message, (int)pcinfo.protocol))) {
						report (LOG_ERR, "%.512s: Page not accepted", p);

						if ((ret == 2) || (ret == 3)) {
							entry->status = STATUS_NODELIVER3 |
								(entry->status & STATUS_NOMAIL) |
								(entry->status & STATUS_SIZETRUNCATE);
								qfAction = QF_DELETE;
								sendReply = 1;
								goto skip1;
						} else { /* whatever else is tmp error */
							entry->status = STATUS_TMPFAIL |
								(entry->status & STATUS_NOMAIL) |
								(entry->status & STATUS_SIZETRUNCATE);
								qfAction = QF_UPDATE;
								goto skip1;
						} 

					} else  {
						entry->status = (entry->status & STATUS_NOMAIL) |
							(entry->status & STATUS_SIZETRUNCATE);

						entry->delivertime = time((time_t*)0L);
						qfAction = QF_UPDATE|QF_DEFER;
						goto skip1;
					}

					break;

				default: /* unreached */

					break;

			} /* switch */

skip1:

			/* if abandonList is set, thats the same as lastEntry below */
			if (abandonList) 
				lastEntry = 1, *rerun = 1;

			entry->status &= ~STATUS_DELIVERING;

			/*
			/* the fate of the queue file is decided by qfAction 
			*/

			/*
			/* if QF_DEFER is set, then the actual confirmation 
			/* that this page really was sent can't be known yet
			/* (ie the protocol needs to shutdown gracefullty first
			/* so, add this queue entry to a list that will be gone
			/* through a second time
			*/

			if (qfAction & QF_DEFER) 
				if (AddToDynString(&deferstring, p, strlen(p))) {
					report (LOG_ERR, "AddToDynString failed for %.512s", p);
					entry->status |= STATUS_NODELIVER2;
					sendReply = 1;
					qfAction = QF_DELETE;
				}
			
			if (qfAction & QF_UPDATE) {

				entry->status |= STATUS_EXPANDED;

				if (UpdateQueueFile(p, fd, queuebuf, (int)statbuf.st_size)) 
					report (LOG_WARNING, "UpdateQueueFile failed for %.512s",
						p);

			} else if (qfAction & QF_DELETE) {

				if (unlink(p) == -1) {
					report (LOG_WARNING, "%.512s: unlink(): %s",  p,
						strerror(errno));
				}
			} else {
				report (LOG_ERR, "Internal error, qfAction unset");
			}

			/* 
			/* send an e-mail reply?
			*/

			/*
			/* (confusing) logic:
			/*
			/*  if QF_DEFER is set, don't send mail - it will be
			/*   sent later -- done.
			/*
			/*  if STATUS_NOMAIL is set don't send mail - they
			/*   don't want a reply -- done.
			/*  
			/*  if sendReply is set send mail -- done.
			/*
			/*  if RETRIES_NOTIFY_FAIL is set, and retries == 
			/*   RETRIES_NOTIFY_FAIL send mail -- done.
			*/

			if (
				((!(qfAction & QF_DEFER)) && (!(entry->status & STATUS_NOMAIL)))
				&& (sendReply || (RETRIES_NOTIFY_FAIL && 
					(entry->retries == RETRIES_NOTIFY_FAIL)))
				) {

				if (DeliverMAIL(sender, p, queuebuf)) {
					report (LOG_WARNING,
						"%.512s: DeliverMail() failed to %.512s", p, sender);
				}
			}


/* cleanup for this loop run */
skip2:			


			/* close the open queue file */
			if (fd != -1)
				if (close (fd) == -1) 
					report (LOG_WARNING, "%.512s: close(): %s", p,
						strerror(errno));

			/*
			/* if this is the last entry in the list
			/* need to do some cleanup
			*/
			if (lastEntry && didProtStartup) {


				switch (pcinfo.protocol) {

					case PC_PET1:
					case PC_PET3:

						report (LOG_INFO, "Ending PET delivery run");
						if 	((ret = EndPETTransaction(mfd, &cbuf))) {
							report (LOG_ERR,
							"%.512s: EndPETTransaction() failed", p);
							if (ret == 2)
								abandonList = 1;
							*rerun = 1;
						}

						HangUpModem(mfd, &cbuf);

						if (close (mfd) == -1)
							report (LOG_WARNING, "%.512s: close(): %s", "modem",
							strerror(errno));

						break;
						

					case PC_NULL:

						break;

					default: /* unreached */

						break;

				} /* switch */

				/*
				/* take care of the deferred messages 
				*/

				/* mark the end of the list */
				if (AddToDynString(&deferstring, &nullbyte, 0)) {
					report (LOG_ERR, "AddToDynString() failed nullbyte");
					/* this should never happen, so this is good enough */
					deferstring.buf = (char*)NULL;
				}

				/* for each entry in that list */
				for (q = deferstring.buf; q && *q; q += strlen(q)+1) {

#ifdef DEBUG
					if (debug > 5)
						report (LOG_INFO, "defer: q=%.512s", q);
#endif /* DEBUG */

					fd = -1;

					/* read in the queue file */
					if ((fd = open(q, O_RDWR, 0)) == -1) {
						report (LOG_WARNING, "Can't open queue file %.512s", p);
						*rerun = 1;
						goto skip3;
					}

					if (fstat(fd, &statbuf) == -1) {
						report (LOG_WARNING, "Can't fstat queue file %.512s",
							p);
						*rerun = 1;
						goto skip3;
					}

					entry = (struct messagequeue*)queuebuf;

					if ((n = read(fd, queuebuf, (int)statbuf.st_size)) == -1) {
						report (LOG_WARNING, "read(%.512s): ", p,
							strerror(errno));
						*rerun = 1;
						goto skip3;
					}

					/* 
					/* if abandonList is set, these really didn't get
					/* delivered
					*/

					if (abandonList) {
						entry->status &= ~STATUS_DELIVERING;
						entry->status &= ~STATUS_DELIVERED;
						entry->status |= STATUS_TMPFAIL;

						if (UpdateQueueFile(p, fd, queuebuf,
							(int)statbuf.st_size)) {
							*rerun = 1;
							goto skip3;
						}

					} /* else they did, and the queue file is not needed */

					if (unlink(q) == -1) {
						report (LOG_WARNING, "%.512s: unlink(): %s",  q,
							strerror(errno));
					}


					/* 
					/* send an e-mail reply as long as STATUS_NOMAIL is not set.
					*/

					/* calculate pointers to text fields */
					recipient = queuebuf+sizeof(struct messagequeue);
					recipient2 = recipient + entry->recipientlen + 1;
					sender = recipient2 + entry->recipient2len + 1;
					message = sender + entry->senderlen + 1;

					entry->status |= STATUS_DELIVERED;

					if (!(entry->status & STATUS_NOMAIL))
						if (DeliverMAIL(sender, q, queuebuf)) 
							report (LOG_WARNING, "%.512s: DeliverMail() failed",
								q);

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

				} /* for each entry in the deferred list */

			} /* lastEntry */

			/*
			/* lastEntry may have been set by an error condition, not
			/* necessarly because this is the last entry...
			*/

			if (lastEntry)
				break;

		} /* for each qfile name (entry) in the delivery list for this pc */

		/* clear ou the defer list between each pc */
		deferstring.bufused = 0;

		if (deferstring.buf)
			deferstring.buf[0] = 0;

	} /* foreach pc */

QueueRunout:

	if (queuebuf)
		free (queuebuf);

	if (dlist) {
		for (n = 0; n < ncentral + 1; ++n)
			if (dlist[n].buf)
				free (dlist[n].buf);
		free (dlist);
	}

	if (deferstring.buf)
		free (deferstring.buf);

	return 0;

} /* QueueRun */

/*********************************************************************/
/* Function: UpdateQueueFile
/*	updates a queue file
/*
/*	Returns	0	good
/*			!0	bad
/*
/*	errors are reported via report()
/*
/*********************************************************************/
UpdateQueueFile(qfname, fd, queuebuf, size)
char *qfname, *queuebuf;
int fd, size;
{

	if (lseek(fd, (off_t)0, SEEK_SET) == -1) {
		report (LOG_WARNING, "lseek(%.512s): %s", qfname, strerror(errno));
		return 1;
	}

	if (write(fd, queuebuf, (int)size) == -1) {
		report (LOG_WARNING, "write(%.512s): %s", qfname, strerror(errno));
		return 1;
	}

	return 0;
} /* UpdateQueueFile */


/*********************************************************************/
/* Function: PetStartup
/*	starts up the pet protocol
/*
/*	Returns	0	good
/*			!0	bad
/*
/*	errors are reported via report()
/*
/*********************************************************************/

PetStartup(pcinfo, mfd, cbuf, p)
struct pcinfo *pcinfo;
int *mfd;
struct cbuf *cbuf;
char *p;

{

	int x, err;

	err = 1; /* assume bad */

	/* assume modem could not be opened */
	*mfd = -1;

	report (LOG_INFO, "Starting up PET protocol");

	/* open and initialize the modem device */
	if (OpenModem(pcinfo, mfd)) {
		*mfd = -1;
		report (LOG_ERR, "%.512s: Can't open modem", p);
		goto pet_startup_fail;
	}

	/* reset modem to known state */
	if (ResetModem(*mfd, cbuf, pcinfo)) {
		report (LOG_ERR, "%.512s: Can't reset modem", p);
		goto pet_startup_fail;
	}

	/* dial out */
	x = DialModem (*mfd, cbuf, pcinfo);

	if ((x != MODEM_RES_C300) && (x != MODEM_RES_C1200) &&
		(x != MODEM_RES_C2400) && (x != MODEM_RES_C4800) &&
		(x != MODEM_RES_C9600) && (x != MODEM_RES_C14400) &&
		(x != MODEM_RES_C38400)) {
		report (LOG_ERR, "%.512s: Not connected - reason %d", p, x);
		goto pet_startup_fail;
	}

	/* start the PET protocol */
	if (StartPET(*mfd, cbuf, pcinfo)) {
		report (LOG_ERR, "%.512s: PET protocol didn't startup", p);
		goto pet_startup_fail;
	}

	err = 0;
	goto PetStartupout;

pet_startup_fail:

	/*
	/* handle startup failure
	*/

	/* send modem hangup command if device opened */
	if (*mfd != -1)
		HangUpModem(*mfd, cbuf);

	/* close the modem device if its opened */
	if (*mfd != -1)
		close (*mfd);

PetStartupout:

	return err;

} /* PetStartup */

