/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

/*
 * ZMailer transport scheduler.
 */

#include <sys/param.h>
#include "hostenv.h"
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include "scheduler.h"
#include <sys/stat.h>
#include <syslog.h>
#include <fcntl.h>
#include <mail.h>
#include NDIR_H
#include "zmsignal.h"

#ifndef	MAXNAMLEN /* POSIX.1 ... */
#define MAXNAMLEN NAME_MAX
#endif

#ifndef	_IOLBF
# define _IOLBF 0200
#endif	/* !_IOLBF */

#ifdef	HONEYBUM/* not really SVID, just this stupid honeywell compiler */
# define MAX_ENTRIES 3000
#else	/* sane pcc */
# define MAX_ENTRIES 10000
#endif	/* honeywell pcc */

struct sptree *spt_mesh[SIZE_L];

#ifdef	MALLOC_TRACE
struct conshell *envarlist = NULL;
#endif	/* MALLOC_TRACE */

char	*progname;
char	*postoffice;
char	*rendezvous;
char	*pidfile = PID_SCHEDULER;
char	*mailshare, *mailbin;
char	*log;
int	mustexit;
int	gotalarm;
int	dumpq;
int	canexit;
int	rereadcf;
int	verbose;
int	dlyverbose = 0;
int	querysocket = -1;	/* fd of TCP socket to listen for queries */
int	subsubdirs;	/* use subdirectories under each channel directory */
int	D_alloc = 0;
int	vtxprep_skip = 0;
int	vtxprep_skip_any = 0;
time_t	next_delaysweep;

#include "memtypes.h"
extern memtypes stickymem;

extern SIGNAL_TYPE sig_exit(), sig_alarm(), sig_iot(), sig_readcf();
extern char *Version;
extern char *optarg;
extern char *progname;
extern char *progname;
extern char *qcf_suffix;
extern char *qlogdir;
extern char *qoutputfile;
extern char *strerror __((int errno));
/* extern char *strchr __((char *s, char c));
   extern char *strrchr __((char *s, char c)); */
extern char *vtx_to_dir __((struct vertex *vp, char *buf));
extern int cistrcmp __((char *s1, char *s2));
extern int efstat __((int fd, struct stat *stbuf));
extern int emkdir __((char *dirpath, int mode));
extern int eopen __((char *filename, int flags, int mode));
extern int eread __((int fd, void *buf, int count));
extern int errno;
extern int estat __((char *filename, struct stat *stbuf));
extern int eunlink __((char *filename));
extern int lockaddr __((int fd, long offset, char tag1, char tag2));
extern int loginit __((void));
extern int match __((char *pattern, char *string));
extern int numkids;
extern int optind;
extern int subsubdirs;
extern int sweepinterval;
extern struct config_entry *readconfig __((char *configfile));
extern struct config_entry *rereadconfig __((struct config_entry *,char *));
extern struct ctlfile *schedule __((int fd, char *file, struct config_entry *ce));
extern struct ctlfile *slurp __((int fd)), *vtxprep __((struct ctlfile *));
extern time_t sweepretry;
extern time_t time();
extern u_char *rfc822date __((time_t *));
extern void deletemsg __((char *, struct ctlfile *));
extern void detach __((void));
extern void die __((int rc, char *reason));
extern void doagenda __((void));
extern void ipcpause __((int pollflag));
extern void killprevious __((int signal, char *pidfile));
extern void link_in __((int flag, struct vertex *vp, u_char *s));
extern void mklink __((char *from, char *to));
extern void mux __((int quickcheck));
extern void prversion __((char *name));
extern void qprint __((int fd));
extern void queryipcinit __((void));
extern void reschedule __((struct vertex *vp, int factor, int index));
extern void syncweb __((char *dir, int nukefile, struct config_entry *ce));
extern void unctlfile __((struct ctlfile *cfp));
extern void vtxdo __((struct vertex *vp, struct config_entry *ce));
extern void vtxprint __((struct vertex *vp));
extern void defaultconfigentry __((struct config_entry *ce, struct config_entry *));


int
main(argc, argv)
	int argc;
	char *argv[];
{
	struct ctlfile *cfp;
	char *config, *cp;
	int i, daemonflg, c, errflg, version, fd;
	long now, offout, offerr;
	struct config_entry *tp;

	/* setlinebuf(stderr);  -- no need for this ? */

	if ((progname = strrchr(argv[0], '/')) == NULL)
		progname = argv[0];
	else
		++progname;

	stickymem = MEM_MALLOC;

	resources_maximize_nofiles();

	postoffice = rendezvous = log = config = NULL;
	daemonflg = 1;
	dlyverbose = 0;
	verbose = subsubdirs = errflg = version = 0;
	while ((c = getopt(argc, argv, "disvf:L:P:Q:VW")) != EOF)
		switch (c) {
		case 'f':	/* override default config file */
			config = optarg;
			break;
		case 'L':	/* override default log file */
			log = optarg;
			break;
		case 'P':	/* override default postoffice */
			postoffice = optarg;
			break;
		case 'Q':	/* override default mail queue rendezvous */
			rendezvous = optarg;
			break;
		case 'v':	/* be verbose and synchronous */
			++verbose;
			daemonflg = 0;
			break;
		case 'W':
			dlyverbose = verbose;
			verbose = 0;
			break;
		case 'd':	/* daemon again */
			daemonflg = 1;
			break;
		case 'i':	/* interactive */
			daemonflg = 0;
			break;
		case 's':
			subsubdirs = 1;
			break;
		case 'V':
			version = 1;
			daemonflg = 0;
			break;
		case '?':
		default:
			errflg++;
			break;
		}
	if (errflg) {
		fprintf(stderr,
			"Usage: %s [-disvV -f configfile -L logfile -P postoffice -Q rendezvous]\n",
			progname);
		exit(128+errflg);
	}
	if ((mailshare = getzenv("MAILSHARE")) == NULL)
		mailshare = MAILSHARE;
	if ((mailbin = getzenv("MAILBIN")) == NULL)
		mailbin = MAILBIN;
	if ((cp = getzenv("LOGDIR")) != NULL)
		qlogdir = cp;
	if (daemonflg && log == NULL) {
		log = emalloc(2 + (u_int)(strlen(qlogdir) + strlen(progname)));
		sprintf(log, "%s/%s", qlogdir, progname);
	}
	if (log != NULL) {
		/* loginit is a signal handler, so can't pass log */
		if (loginit() < 0)	/* do setlinebuf() there */
			die(1, "log initialization failure");
		/* close and reopen log files */
		SIGNAL_HANDLE(SIGHUP, (SIGNAL_TYPE (*)())loginit);
	} else {
		SIGNAL_IGNORE(SIGHUP); /* no surprises please */
		setvbuf(stdout, (char *)NULL, _IOLBF, 0);
		setbuf(stderr,NULL); /* [mea] No buffering on stdout.. */
	}
#ifdef	SIGUSR1
	SIGNAL_HANDLE(SIGUSR1, sig_readcf);
#endif	/* SIGUSR1 */
	if (verbose || version) {
		prversion("scheduler");
		if (version)
			exit(0);
		putc('\n', stderr);
	}
	offout = ftell(stdout);
	offerr = ftell(stderr);
	if (config == NULL) {
		config = emalloc(3 + (u_int)(strlen(mailshare)
					     + strlen(progname)
					     + strlen(qcf_suffix)));
		sprintf(config, "%s/%s.%s", mailshare, progname, qcf_suffix);
	}
	if ((tp = readconfig(config)) == NULL) {
		cp = emalloc(strlen(config)+50);
		sprintf(cp, "null control file: %s", config);
		die(1, cp);
		/* NOTREACHED */
	}

	if (postoffice == NULL && (postoffice = getzenv("POSTOFFICE")) == NULL)
		postoffice = POSTOFFICE;
	if (chdir(postoffice) < 0 || chdir(SCHEDULERDIR) < 0)
		fprintf(stderr, "%s: cannot chdir to %s/%s.\n",
			progname, postoffice, SCHEDULERDIR);
	if (rendezvous == NULL && (rendezvous = getzenv("RENDEZVOUS")) == NULL)
		rendezvous = qoutputfile;
	if (daemonflg) {
		/* X: check if another daemon is running already */
		if (!verbose
		    && (offout < ftell(stdout) || offerr < ftell(stderr))) {
			fprintf(stderr, "%ld %ld %ld %ld\n", offout, ftell(stdout),
				offerr, ftell(stderr));
			fprintf(stderr, "%s: daemon not started.\n", progname);
			die(1, "too many scheduler daemons");
			/* NOTREACHED */
		}
		detach();		/* leave worldy matters behind */
		now = time((long *)0);
		printf("%s: scheduler daemon (%s)\n\tstarted at %s\n",
		       progname, Version, (char *)rfc822date(&now));
		killprevious(SIGTERM, pidfile);
	}
	for (i = 0; i < SIZE_L; ++i)
		spt_mesh[i] = sp_init();
#ifdef  LOG_PID
#ifdef  LOG_MAIL
	openlog("scheduler", LOG_PID, LOG_MAIL);
#else   /* !LOG_MAIL */
	openlog("scheduler", LOG_PID);
#endif  /* LOG_MAIL */
#endif  /* LOG_PID */
	if (optind < argc) {
		/* process the specified control files only */
		for (; optind < argc; ++optind) {
			if ((fd = eopen(argv[optind], O_RDWR, 0)) < 0)
				continue;
			/* the close(fd) is done in vtxprep */
			cfp = schedule(fd, argv[optind], tp);
			if (cfp == NULL) {
			  if (verbose)
			    fprintf(stderr, "Nothing scheduled for %s!\n",
				    argv[optind]);
			} else
			  eunlink(argv[optind]);
		}
		doagenda();
		exit(0);
	}
	mustexit = gotalarm = dumpq = rereadcf = 0;
	canexit = 0;
	SIGNAL_IGNORE(SIGPIPE);
	SIGNAL_HANDLE(SIGALRM, sig_alarm);	/* process agenda */
	SIGNAL_HANDLE(SIGUSR2, sig_iot);	/* dump queue info */
	cp = emalloc(strlen(TRANSPORTDIR)+4);
	sprintf(cp, "../%s", TRANSPORTDIR);
	queryipcinit();
	vtxprep_skip_any = 0;
	syncweb(cp, 0, tp);
	if (!vtxprep_skip_any) {
	  free(cp);
	  cp = NULL;
	}
	if (dlyverbose) verbose = dlyverbose;
	time(&next_delaysweep);
	next_delaysweep += 1*60; /* One minute */

	canexit = 1;
	SIGNAL_HANDLE(SIGTERM, sig_exit);	/* split */
	sweepretry = 1;
	alarm(sweepinterval);
#ifdef	MALLOC_TRACE
	mal_leaktrace(1);
#endif	/* MALLOC_TRACE */
	while (!mustexit) {
		canexit = 0;
		if (cp != NULL &&  time(NULL) > next_delaysweep) {
		  /* Delayed jobs in queue.. */
		  vtxprep_skip_any = 0;
		  fprintf(stderr,"Delay-sweeping previously locked files\n");
		  syncweb(cp, 0, tp);
		  if (!vtxprep_skip_any) {
		    free(cp);
		    cp = NULL;
		  }
		  time(&next_delaysweep);
		  next_delaysweep += 5*60; /* Five minutes */
		}
		syncweb(".", 1, tp);
		doagenda();
		/*
		 * Due to a possible race condition, gotalarm = 0 must
		 * have been done inside doagenda() instead of here.
		 */
		/* mux on the stdout of children */
		while (!gotalarm && numkids > 0) {
			if (dumpq) {
				qprint(-1);
				dumpq = 0;
			}
			if (rereadcf) {
				tp = rereadconfig(tp, config);
				rereadcf = 0;
			}
			/* if (numkids > 0) */
			mux(0);
		}
		canexit = 1; /* may be possible to move this after doagenda() */
		/* wait for an alarm call */
		while (!gotalarm) {
			if (dumpq) {
				qprint(-1);
				dumpq = 0;
			}
			if (rereadcf) {
				tp = rereadconfig(tp, config);
				rereadcf = 0;
			}
			/* ipcpause(0); */
			mux(0);
		}
	}
#ifdef	MALLOC_TRACE
	mal_dumpleaktrace(stderr);
#endif	/* MALLOC_TRACE */
	if (mustexit)
		die(0, "signal");
	exit(0);
	/* NOTREACHED */
	return 0;
}

SIGNAL_TYPE
sig_exit()
{
	
	if (querysocket >= 0) {		/* give up mailq socket asap */
		close(querysocket);
		querysocket = -1;
	}
	if (canexit)
		die(0, "signal");
	mustexit = 1;
}

SIGNAL_TYPE
sig_alarm()
{
	gotalarm = 1;
	SIGNAL_HANDLE(SIGALRM, sig_alarm);
}

#ifdef SIGUSR2
SIGNAL_TYPE
sig_iot()
{
	dumpq = 1;
	SIGNAL_HANDLE(SIGUSR2, sig_iot);
}
#endif

#ifdef SIGUSR1
SIGNAL_TYPE
sig_readcf()
{
	rereadcf = 1;
	SIGNAL_HANDLE(SIGUSR1, sig_readcf);
}
#endif

/*
 * Absorb any new files that showed up in our directory.
 */

void
syncweb(dir, nukefile, cehead)
	char *dir;
	int nukefile;
	struct config_entry *cehead;
{
	struct stat stbuf;
	int fd;
	char file[MAXNAMLEN+1];
	DIR *dirp;
	struct NDIR_TYPE *dp;
	static time_t modtime = 0;

	if (estat(dir, &stbuf) < 0)
	  return;
	if (stbuf.st_mtime == modtime)
	  return;
	modtime = stbuf.st_mtime;
	dirp = opendir(dir);
	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
	  if (mustexit)
	    break;

	  /* ipcpause(1); */
	  mux(1);

	  if (isascii(dp->d_name[0]) && isdigit(dp->d_name[0])) {
	    if (strcmp(dir, ".") == 0)
	      strcpy(file, dp->d_name);
	    else
	      sprintf(file, "%s/%s", dir, dp->d_name);
	    if ((fd = eopen(file, O_RDWR, 0)) < 0) {
	      if (getuid() == 0)
		eunlink(file);	/* hrmpf! */
	    } else {
	      if (!nukefile) {
		/* If we are re-syncing in from the $POSTOFFICE/transports/
		   directory, we may have this file in processing state...  */
		struct spblk *spl;
		fstat(fd,&stbuf);
		spl = sp_lookup((u_int)stbuf.st_ino, spt_mesh[L_CTLFILE]);
		if (spl != NULL) {
		  /* Already in processing, don't touch.. */
		  /*printf("File: %s active (not previously locked)\n",file);*/
		  close(fd);
		  vtxprep_skip_any |= 1;
		  continue;
		}
		/* Ok, perhaps it is not in active processing, sync it in.. */
	      }
	      schedule(fd, file, cehead);
	      if (nukefile)
		unlink(file);
	      else		/* we're scanning TRANSPORTDIR */
		/* unlink it from the SCHEDULERDIR */
		unlink(dp->d_name);
	      /*
	       * This avoids a race when the
	       * scheduler starts up while the router
	       * is pushing stuff at it.  Note that
	       * there is a window of vulnerability
	       * if the router links to transport
	       * then scheduler instead of vice
	       * versa.
	       */
	    }
	  }
	}
#ifdef	BUGGY_CLOSEDIR
	/*
	 * Major serious bug time here;  some closedir()'s
	 * free dirp before referring to dirp->dd_fd. GRRR.
	 * XX: remove this when bug is eradicated from System V's.
	 */
	close(dirp->dd_fd);
#endif
	closedir(dirp);
}

/*
 * The schedule() function is in charge of reading the control file from
 * the scheduler directory, and creating all the appropriate links to the
 * control file.
 * Since it is convenient to do so here, it also goes through the list
 * of transport directives and schedules the appropriate things to be
 * done at the appropriate time in the future.
 */
struct ctlfile *
schedule(fd, file, cehead)
	int fd;
	char *file;
	struct config_entry *cehead;
{
	struct ctlfile *cfp;
	char path[MAXPATHLEN], buf[MAXPATHLEN], *dir;
	struct vertex *vp;

	/* read and process the control file */
	if ((cfp = vtxprep(slurp(fd))) == NULL) {
	  if (!vtxprep_skip)	/* Unless skipped.. */
	    eunlink(file);	/* everything here has been processed */
	  vtxprep_skip_any |= vtxprep_skip;
	  return NULL;
	}
	if (cfp->head == NULL) {
	  unctlfile(cfp);
	  return NULL;
	}
	/* -- DO NOT FREE CFP->CONTENTS YET! 

	   if (cfp->contents != NULL && cfp->vfp == NULL) {
	   free(cfp->contents);
	   cfp->contents = NULL;
	   }
	*/
	for (vp = cfp->head; vp != NULL; vp = vp->next[L_CTLFILE]) {
	  if (subsubdirs) {	/* channel/host/file */
	    dir = vtx_to_dir(vp, buf);
	  } else if (vp->next[L_CTLFILE] == NULL
		     || (vp->orig[L_CHANNEL] !=
			 vp->next[L_CTLFILE]->orig[L_CHANNEL])) {
	    dir = vp->orig[L_CHANNEL]->name; /* channel/file */
	  } else
	    dir = NULL;
	  if (dir != NULL) {
	    sprintf(path, "%s/%lu", dir, cfp->id);
	    mklink(file, path);
	    if (cfp->vfp != NULL)
	      fprintf(cfp->vfp, "linked to %s\n", path);
	    if (verbose)
	      printf("linked %s,%ld to %s\n",
		     vp->cfp->mid, vp->cfp->id, path);
	  }

	  vtxdo(vp, cehead);
	  if (cfp->vfp)
	    fseek(cfp->vfp, 0, 2);
	}
	return cfp;
}

struct ctlfile *
slurp(fd)
	int fd;
{
	register char *s;
	register int i;
	char *contents;
	long *offset, *ip, *lp;
	int offsetspace;
	struct stat stbuf;
	struct ctlfile *cfp;

	if (fd < 0)
	  return NULL;
	if (efstat(fd, &stbuf) < 0) {
	  close(fd);
	  return NULL;
	}
	if (!S_ISREG(stbuf.st_mode)) {
	  close(fd);
	  return NULL;	/* XX: give error */
	}
	contents = emalloc((u_int) stbuf.st_size+1);
	if (eread(fd, contents, stbuf.st_size) != stbuf.st_size) { /* slurp */
	  close(fd);
	  return NULL;
	}
	contents[stbuf.st_size] = 0;

	cfp = (struct ctlfile *)emalloc((u_int) (sizeof(struct ctlfile)));
	memset((void*)cfp, 0, sizeof(struct ctlfile));

	cfp->fd = fd;
	cfp->vfp = NULL;
	cfp->uid = stbuf.st_uid;
	cfp->mark = V_NONE;
	cfp->haderror = 0;
	cfp->contents = contents;
	cfp->envid    = NULL;
	cfp->logident = NULL;
	cfp->erroraddr = NULL;
	cfp->msgbodyoffset = 0;

	/* go through the file and mark it off */
	i = 0;
	offsetspace = 100;
	offset = (long*)emalloc(sizeof(long)*offsetspace);
	offset[i++] = 0L;
	for (s = contents; s - contents < stbuf.st_size; ++s) {
	  if (*s == '\n') {
	    *s++ = '\0';
	    if (s - contents < stbuf.st_size) {
	      if (i >= offsetspace-1) {
		offsetspace += 20;
		offset = (long*)erealloc(offset,sizeof(long)*offsetspace);
	      }
	      offset[i++] = s - contents;
	      if (*s == _CF_MSGHEADERS) {
		/* find a \n\n combination */
		while (!(*s == '\n' && *(s+1) == '\n'))
		  if (s-contents < stbuf.st_size)
		    ++s;
		  else
		    break;
		if (s - contents >= stbuf.st_size) {
		  /* XX: header ran off file */
		}
	      } else if (*s == _CF_BODYOFFSET)
		cfp->msgbodyoffset = atoi(s+2);
	      else if (*s == _CF_DSNENVID)
		cfp->envid = s+2;
	      else if (*s == _CF_MESSAGEID)
		cfp->mid = s+2;
	      else if (*s == _CF_LOGIDENT)
		cfp->logident = s+2;
	      else if (*s == _CF_ERRORADDR)
		cfp->erroraddr = s+2;
	    }
	  }
	}
	cfp->nlines = i;
	/* closing fd must be done in vtxprep(), so we can unlock stuff easy */
	cfp = (struct ctlfile *)erealloc((void*)cfp,
					 (u_int) (sizeof(struct ctlfile) +
						  i * (sizeof offset[0])));
	lp = &(cfp->offset[0]);
	ip = &(offset[0]);
	/* copy over the offsets */
	while (--i >= 0)
	  *lp++ = *ip++;
	cfp->id = stbuf.st_ino;
	cfp->mid = NULL;
	free(offset);	/* release the block */
	return cfp;
}

struct offsort {
	long	offset;
	int	myidx;
	long	headeroffset;
	long	drptoffset;
};

/* Used by the qsort comparison routine, bcp points to the control file bytes */
static char *bcp;

int
bcfcn(a, b)
	struct offsort *a, *b;
{
	return cistrcmp(bcp + a->offset, bcp + b->offset);
}


static int
lockverify(cfp,cp)		/* Return 1 when lock process does not exist */
	struct ctlfile *cfp;	/* Call only when the lock is marked active! */
	char *cp;
{
	char	lockbuf[1+_CFTAG_RCPTPIDSIZE];
	int	lockpid;

	++cp;
	if (!(*cp == ' ' ||
	      (*cp >= '0' && *cp <= '9'))) return 1; /* Old-style entry */
	memcpy(lockbuf,cp,_CFTAG_RCPTPIDSIZE);
	lockbuf[sizeof(lockbuf)-1] = 0;
	if (sscanf(lockbuf,"%d",&lockpid) != 1) return 1; /* Bad value ? */
	if (kill(lockpid,0) != 0) return 1; /* PID does not exist, or
					       other error.. */
	fprintf(stderr,"lockverify: Lock with PID=%d is active on %s:%s\n",
		lockpid, cfp->logident, cp+_CFTAG_RCPTPIDSIZE);
	return 0;	/* Locking PID does exist.. */
}

struct ctlfile *
vtxprep(cfp)
	struct ctlfile *cfp;
{
	register int i, opcnt;
	register long *lp;
	int svn;
	char *cp, *channel, *host, *l_channel, *l_host;
	char *echannel, *ehost, *l_echannel, *l_ehost, mfpath[100], flagstr[2];
	struct spblk *spl;
	struct vertex *vp, *pvp, **pvpp, *head;
	struct stat stbuf;
	struct offsort *offarr = NULL;
	int offspc;

	if (cfp == NULL)
	  return NULL;

	sp_install((u_int)cfp->id, (u_char *)cfp, 0, spt_mesh[L_CTLFILE]);
	channel = host = NULL;
	/* copy offsets into local array */
	offspc = 16;
	offarr = (struct offsort *)emalloc(offspc * sizeof(struct offsort));
	opcnt = 0;
	svn = 0;
	vtxprep_skip = 0;
	lp = &cfp->offset[0];
	for (i = 0; i < cfp->nlines; ++i, ++lp) {
	  cp = cfp->contents + *lp + 1;
	  if (*cp == _CFTAG_LOCK) {
	    /*
	     * This is assumed to happen when we move old control
	     * files into the scheduler directory. Since this is
	     * the only processing that needs to be done except
	     * for the rename(), we might as well do it here,
	     * instead of in some separate program.
	     *
	     * NB! If transport agents are running, this may
	     * cause duplicate delivery of a message.  Better than
	     * no delivery...
	     */
	    if (!lockverify(cfp,cp)) {
	      /*
	       * IMO we are better off by forgetting for a while that
	       * this spool-file exists at all.  Thus very least we
	       * won't errorneously delete it.
	       */
	      close(cfp->fd);	/* Was opened on  schedule() */
	      spl = sp_lookup((u_int)cfp->id, spt_mesh[L_CTLFILE]);
	      if (spl != NULL)
		sp_delete(spl, spt_mesh[L_CTLFILE]);
	      if (cfp->vfp != NULL) {
		fprintf(cfp->vfp,
			"New scheduler: Skipped a job-file because it is held locked by PID=%6.6s\n",cp+1);
		fclose(cfp->vfp);
	      }
	      free(cfp->contents);
	      free((char *)cfp);
	      vtxprep_skip = 1;
	      return NULL;
	    }
	    *cp = _CFTAG_NORMAL; /* unlock it */
	    lockaddr(cfp->fd, (long) (cp - cfp->contents),
		     _CFTAG_LOCK, _CFTAG_NORMAL);
	  }
	  if (*cp == _CFTAG_NORMAL
	      || *cp == '\n' /* This appears for msg-header entries.. */ ) {
	    --cp;
	    switch (*cp) {
	    case _CF_RECIPIENT:
	      if (opcnt >= offspc-1) {
		offspc *= 2;
		offarr = (struct offsort *)erealloc(offarr,
						    sizeof(struct offsort) *
						    offspc);
	      }
	      offarr[opcnt].offset = *lp + 2;
	      cp += 2;
	      if (*cp == ' ' || (*cp >= '0' && *cp <= '9')) {
		/* New PID locking scheme.. */
		offarr[opcnt].offset += _CFTAG_RCPTPIDSIZE;
	      }
	      offarr[opcnt].myidx = i;
	      offarr[opcnt].headeroffset = -1;
	      offarr[opcnt].drptoffset = -1;
	      ++opcnt;
	      break;
	    case _CF_RCPTNOTARY:
	      /* XX: IETF-NOTARY-DRPT DATA! */
	      offarr[svn].drptoffset = *lp + 2;
#if 0 /* ---------------------------------------------------------------- */
	      /* This fragment is pulled from transports/libta/ctlopen.c */
	      ++s;
	      while (*s) {
		while (*s && (*s == ' ' || *s == '\t')) ++s;
		if (cistrncmp("RET=",s,4)==0) {
		  s += 4;
		  if (cistrncmp("YES",s,3)==0) {
		    rp->dsnflags |= _DSN_RET_YES;
		    s += 3;
		  } else if (cistrncmp("NO",s,2)==0) {
		    rp->dsnflags |= _DSN_RET_NO;
		    s += 2;
		  } else 
		    /* Not YES/NO ??? */
		    ;
		  while (*s && *s != ' ' && *s != '\t') ++s;
		  continue;
		}
		if (cistrncmp("NOTIFY=",s,7)==0) {
		  s += 7;
		  if (cistrncmp("SUCCESS",s,7)==0) {
		    rp->dsnflags |= _DSN_NOTIFY_SUCCESS;
		    s += 7;
		  } else if (cistrncmp("FAILURE",s,7)==0) {
		    rp->dsnflags |= _DSN_NOTIFY_FAILURE;
		    s += 7;
		  } else if (cistrncmp("ALWAYS",s,6)==0) {
		    rp->dsnflags |= _DSN_NOTIFY_ALWAYS;
		    s += 6;
		  } else if (cistrncmp("NEVER",s,5)==0) {
		    rp->dsnflags |= _DSN_NOTIFY_NEVER;
		    s += 5;
		  } else
		    /* None of the above ?? */
		    ;
		  while (*s && *s != ' ' && *s != '\t') ++s;
		  continue;
		}
		if (cistrncmp("ORCPT=",s,6)==0) {
		  s += 6;
		  rp->orcpt = s;
		  while (*s && *s != ' ' && *s != '\t') ++s;
		  if (*s) *s++ = 0;
		  continue;
		}
	      }
#endif /* ---------------------------------------------------------------- */
	      break;
	    case _CF_MSGHEADERS:
	      for (/* we count up.. */; svn < opcnt; ++svn)
		offarr[svn].headeroffset = *lp + 2;
	      break;
	    case _CF_DSNENVID:
	      cfp->envid = cp+2;
	      break;
	    case _CF_DIAGNOSTIC:
	      cfp->haderror = 1;
	      break;
	    case _CF_MESSAGEID:
	      cfp->mid = cp+2;
	      break;
	    case _CF_BODYOFFSET:
	      cfp->msgbodyoffset = atoi(cp+2);
	      break;
	    case _CF_LOGIDENT:
	      cfp->logident = cp+2;
	      break;
	    case _CF_ERRORADDR:
	      cfp->erroraddr = cp+2;
	      break;
	    case _CF_OBSOLETES:
	      deletemsg(cp+2, cfp);
	      break;
	    case _CF_VERBOSE:
#ifdef	USE_SETREUID
	      setreuid(0, cfp->uid);
#else	/* SVID ?? */
#ifdef	USE_SETEUID
	      seteuid(cfp->uid);
#else
	      /* Only reversible under recent SVID! */
	      setuid(cfp->uid);
#endif	/* !USE_SETEUID */
#endif	/* !USE_SETREUID */
	      cfp->vfp = fopen(cp+2, "a+");
#ifdef	USE_SETREUID
	      setreuid(0, 0);
#else	/* SVID ?? */
#ifdef	USE_SETEUID
	      seteuid(0);
#else
	      /* Only works under recent SVID! */
	      setuid(0);
#endif	/* !USE_SETEUID */
#endif	/* !USE_SETREUID */
	      if (cfp->vfp != NULL) {
		fseek(cfp->vfp, (long)0, 2);
		setvbuf(cfp->vfp, (char *)NULL,	_IOLBF, 0);
	      }
	      if (cfp->vfp != NULL && cfp->mid != NULL)
		fprintf(cfp->vfp,
			"scheduler processing %s\n",
			cfp->mid);
	      break;
	    }
	  }
	}
	close(cfp->fd);	/* closes the fd opened in schedule() */
	if (cfp->mid != NULL)
	  sprintf(mfpath, "../%s/%s", QUEUEDIR, cfp->mid);
	if (cfp->mid == NULL || cfp->logident == NULL
	    || estat(mfpath, &stbuf) < 0) {
	  spl = sp_lookup((u_int)cfp->id, spt_mesh[L_CTLFILE]);
	  if (spl != NULL)
	    sp_delete(spl, spt_mesh[L_CTLFILE]);
	  if (cfp->vfp != NULL) {
	    fprintf(cfp->vfp,
		    "aborted due to missing information\n");
	    fclose(cfp->vfp);
	  }
	  free(cfp->contents);
	  free((char *)cfp);
	  return NULL;
	}
	cfp->ctime = stbuf.st_ctime;
	cfp->fd = -1;
	/* sort it to get channels and channel/hosts together */
	bcp = cfp->contents;
	qsort((char *)offarr, opcnt, sizeof (struct offsort), bcfcn);
	/*
	 * Loop through them; whenever either channel or host changes,
	 * make a new vertex. All such vertices must be linked together.
	 */
	flagstr[0] = ' ';
	flagstr[1] = '\0';
	l_channel = l_echannel = l_host = l_ehost = flagstr;
	svn = 0;
	pvp = NULL;
	head = NULL;
	pvpp = &head;
	for (i = 0; i < opcnt; ++i) {
	  channel = bcp + offarr[i].offset /* - 2 */;
	  if ((echannel = strchr(channel, ' ')) == NULL) /* error! */
	    continue;
	  *echannel = '\0';
	  host = echannel + 1;
	  while (isascii(*host) && isspace(*host))
	    ++host;
#if 1
	  /* [mea]   channel ((mx.target)(mx.target mx.target)) rest.. */
	  cp = host;
	  if (*cp == ')') {
	    while(*cp == '(')
	      ++cp;
	    while(*cp && *cp != ' ' && *cp != '\t' && *cp != ')')
	      ++cp;
	    if (*cp)
	      ehost = cp;
	    else
	      continue;		/* error! */
	    /* Ok, found ehost, now parsing past parenthesis.. */
	    cp = host;
	    while(*host == '(')  ++host;
	    if (*cp == '(') {
	      ++cp;
	      while(*cp == '(') {
		/* Find ending ')', and skip it */
		while(*cp && *cp != ')') ++cp;
		if (*cp == ')') ++cp;
	      }
	      /* Ok, scanned past all inner parenthesis, now ASSUME
		 next one is ending outer parenthesis, and skip it */
	      ++cp;
	    }
	    if (*cp != ' ' && *cp != '\t')
	      continue;		/* Not proper separator.. error! */
	  } else
#endif
	    if ((ehost = strchr(host, ' ')) == NULL) /* error! */
	      continue;

	  *ehost = '\0';
	  /* compare with the last ones */
	  if (cistrcmp(channel, l_channel) || cistrcmp(host, l_host)) {
	    /* wrap and tie the old vertex node */
	    if (i != svn) {
	      u_int alloc_size = (u_int) (sizeof (struct vertex) +
					  (i - svn - 1) * sizeof (int));
	      vp = (struct vertex *)emalloc(alloc_size);
	      memset((char*)vp, 0, alloc_size);
	      vp->cfp = cfp;
	      defaultconfigentry(&vp->ce,NULL);
	      vp->next[L_CTLFILE] = NULL;
	      vp->prev[L_CTLFILE] = pvp;
	      vp->message = NULL;
	      vp->retryindex = 0;
	      vp->nextitem = NULL;
	      vp->previtem = NULL;
	      vp->wakeup = 0;
	      vp->proc = NULL;
	      vp->ngroup = i - svn;
	      vp->attempts = 0;
	      vp->notary = NULL;
	      vp->headeroffset = offarr[i].headeroffset; /* Just any of them will do */
	      vp->drptoffset = offarr[i].drptoffset;
	      while (svn < i) {
		vp->index[i-svn-1] = offarr[svn].myidx;
		++svn;
	      }
	      *pvpp = vp;
	      pvpp = &vp->next[L_CTLFILE];
	      pvp = vp;
	      link_in(L_HOST, vp, (u_char *)l_host);
	      link_in(L_CHANNEL, vp, (u_char *)l_channel);
	    }
	    /* create a new vertex node */
	    svn = i;
	  }
	  /* stick the current 'r'eceived line into the current vertex */
	  /* restore the characters */
	  *l_echannel = *l_ehost = ' ';
	  l_echannel = echannel;
	  l_ehost = ehost;
	  l_channel = channel;
	  l_host = host;
	}
	/* wrap and tie the old vertex node (this is a copy of code above) */
	if (i != svn) {
	  u_int alloc_size = (u_int) (sizeof (struct vertex) +
				      (i - svn - 1) * sizeof (int));
	  vp = (struct vertex *)emalloc(alloc_size);
	  memset((void*)vp, 0, alloc_size);
	  vp->cfp = cfp;
	  defaultconfigentry(&vp->ce,NULL);
	  vp->next[L_CTLFILE] = NULL;
	  vp->prev[L_CTLFILE] = pvp;
	  vp->message = NULL;
	  vp->retryindex = 0;
	  vp->nextitem = NULL;
	  vp->previtem = NULL;
	  vp->proc = NULL;
	  vp->wakeup = 0;
	  vp->ngroup = i - svn;
	  vp->attempts = 0;
	  vp->notary = NULL;
	  vp->headeroffset = offarr[svn].headeroffset; /* Just any of them will do */
	  vp->drptoffset = offarr[svn].drptoffset;
	  while (svn < i) {
	    vp->index[i-svn-1] = offarr[svn].myidx;
	    ++svn;
	  }
	  *pvpp = vp;
	  pvpp = &vp->next[L_CTLFILE];
	  pvp = vp;
	  link_in(L_HOST, vp, (u_char *)host);
	  link_in(L_CHANNEL, vp, (u_char *)channel);
	}
	*l_echannel = *l_ehost = ' ';
	/*
	   for (vp = head; vp != NULL; vp = vp->next[L_CTLFILE]) {
	     printf("--\n");
	     for (i = 0; i < vp->ngroup; ++i)
	       printf("\t%s\n", cfp->contents+cfp->offset[vp->index[i]]);
	   }
	*/
	cfp->head = head;
	return cfp;
}

int
vtxfill(vp, tp)
	struct vertex *vp;
	struct config_entry *tp;
{
	/* if the channel doesn't match, there's no hope! */
	if (verbose>1)
	  printf("ch? %s %s\n",
		 vp->orig[L_CHANNEL]->name, tp->channel);
	if (tp->channel[0] == '*' && tp->channel[1] == '\0')
	  return 0; /* Never match the defaults entry! */
	if (!match(tp->channel, vp->orig[L_CHANNEL]->name))
	  return 0;

	if (!(tp->host == NULL || tp->host[0] == '\0'
	      || (tp->host[0] == '*' && tp->host[1] == '\0'))) {
	  if (!match(tp->host, vp->orig[L_HOST]->name))
	    return 0;
	}

	if (verbose>1)
	  printf("host %s %s\n", vp->orig[L_HOST]->name, tp->host);

	if (tp->interval != -1) vp->ce.interval = tp->interval;
	if (tp->expiry   != -1) vp->ce.expiry = tp->expiry;
	vp->ce.expiryform     = tp->expiryform;
	vp->cfp->deliveryform = tp->deliveryform;
	if (tp->uid      != -1) vp->ce.uid = tp->uid;
	if (tp->gid      != -1) vp->ce.gid = tp->gid;
	if (tp->maxkids  != -1) vp->ce.maxkids = tp->maxkids;
	if (tp->maxkidChannel != -1) vp->ce.maxkidChannel = tp->maxkidChannel;
	if (tp->maxkidHost != -1) vp->ce.maxkidHost = tp->maxkidHost;
	if (tp->nretries > 0) {
	  vp->ce.nretries = tp->nretries;
	  vp->ce.retries = tp->retries;
	}
	if (tp->command != NULL) {
	  vp->ce.command = tp->command;
	  vp->ce.argv = tp->argv;
	}
	if (tp->bychannel != -1)
	  vp->ce.bychannel = tp->bychannel;
	if (tp->byhost != -1)
	  vp->ce.byhost = tp->byhost;
	vp->ce.flags |= tp->flags;
	vp->ce.mark = vp->ce.pending = 0;
	if (tp->skew > 0) vp->ce.skew = tp->skew;
	return 1;
}

void
vtxdo(vp, cehead)
	struct vertex *vp;
	struct config_entry *cehead;
{
	struct config_entry *tp;
	int n;
	int cnt = 0;

	/*
	 * go through scheduler control file entries and
	 * fill in the blanks in the vertex specification
	 */
	n = 0;
	for (tp = cehead; tp != NULL; tp = tp->next) {
	  ++cnt;
	  n += vtxfill(vp, tp);
	  if (vp->ce.command != NULL)
	    break;
	}
	if (n == 0) {
	  fprintf(stderr,
		  "%s: no pattern matched %s/%s address\n",
		  progname,
		  vp->orig[L_CHANNEL]->name, vp->orig[L_HOST]->name);
	  /* XX: memory leak here? */
	  return;
	}
	if (verbose)
	  printf("Matched %dth config entry with: %s/%s\n", cnt,
		 vp->orig[L_CHANNEL]->name, vp->orig[L_HOST]->name);


	/* set default values */
	if (vp->ce.expiry > 0)
	  vp->ce.expiry += vp->cfp->ctime;
	else
	  vp->ce.expiry = 0;
	if (vp->ce.interval == -1)
	  vp->ce.interval = 3600;
	if (vp->ce.maxkids == -1)
	  vp->ce.maxkids = 1000;
	if (vp->ce.maxkidChannel == -1)
	  vp->ce.maxkidChannel = 1000;
	if (vp->ce.maxkidHost == -1)
	  vp->ce.maxkidHost = 1000;
	if (vp->ce.byhost == -1)
	  vp->ce.byhost = 0;
	if (vp->ce.bychannel == -1)
	  vp->ce.bychannel = 0;

	if (verbose>1)
	  vtxprint(vp);

	if ((vp->ce.flags & FF_GANGSCHEDULE)
	    && vp->prev[L_HOST] != NULL
	    && vp->prev[L_CHANNEL] != NULL
	    && vp->ce.retries == vp->prev[L_HOST]->ce.retries
	    && vp->orig[L_HOST] == vp->prev[L_HOST]->orig[L_HOST]
	    && vp->orig[L_CHANNEL] == vp->prev[L_CHANNEL]->orig[L_CHANNEL]) {
	  vp->retryindex = vp->prev[L_HOST]->retryindex;
	  reschedule(vp, -(vp->prev[L_HOST]->wakeup), -1);
	} else
	  reschedule(vp, 0, -1);
}

/* Shell-GLOB-style matching */
int
match(pattern, string)
	register char	*pattern;
	register char	*string;
{
	while (1) {
	  switch (*pattern) {
	  case '*':
	    pattern++;
	    do {
	      if (match(pattern, string))
		return 1;
	    } while (*string++ != '\0');
	    return 0;
	  case '[':
	    if (*string == '\0')
	      return 0;
	    if (*(pattern+1) == '^') {
	      ++pattern;
	      while ((*++pattern != ']')
		     && (*pattern != *string))
		if (*pattern == '\0')
		  return 0;
	      if (*pattern != ']')
		return 0;
	      string++;
	      break;
	    }
	    while ((*++pattern != ']') && (*pattern != *string))
	      if (*pattern == '\0')
		return 0;
	    if (*pattern == ']')
	      return 0;
	    while (*pattern++ != ']')
	      if (*pattern == '\0')
		return 0;
	    string++;
	    break;
	  case '?':
	    pattern++;
	    if (*string++ == '\0')
	      return 0;
	    break;
	  case '\0':
	    return (*string == '\0');
	  default:
	    if (*pattern++ != *string++)
	      return 0;
	  }
	}
}

/*
 * This routine links a group of addresses (described by what vp points at)
 * into the Tholian Web (err, our matrix). The flag (either L_HOST or
 * L_CHANNEL) selects a namespace of strings pointed at by s. It just
 * happens we need 2 (host and channel names), and we don't care what
 * they are as long as they are in separate spaces.
 */
void
link_in(flag, vp, s)
	int flag;
	struct vertex *vp;
	u_char *s;
{
	register u_char *cp;
	struct web *wp;
	struct spblk *spl;

	for (cp = s; *cp != '\0'; ++cp)
	  if (isascii(*cp) && isupper(*cp))
	    *cp = tolower(*cp);
	spl = sp_lookup(symbol(s), spt_mesh[flag]);
	if (spl == NULL || (wp = (struct web *)spl->data) == NULL) {
	  wp = (struct web *)emalloc(sizeof (struct web));
	  memset((void*)wp, 0, sizeof (struct web));
	  sp_install(symbol(s), (u_char *)wp, 0, spt_mesh[flag]);
	  wp->name = strsave((char *)s);
	  wp->kids = 0;
	  wp->link = NULL;
	  wp->lastlink = NULL;
	  wp->fifohead = NULL;
	  wp->fifotail = NULL;
	}
	vp->next[flag] = NULL;
	vp->prev[flag] = wp->lastlink;
	vp->orig[flag] = wp;
	if (wp->lastlink != NULL)
	  wp->lastlink->next[flag] = vp;
	wp->lastlink = vp;
	if (wp->link == NULL)
	  wp->link = vp;
}


/*
 * Generate a file name corresponding to a link to the control file.
 * On busy systems, this should probably map into a channel/host/file
 * pathname. Unfortunately, some OS's cannot handle long host names.
 * If that becomes a problem, it should be possible to select another
 * scheme on a per-channel basis. It should be kept in mind that any
 * scheme which produces an N-to-M reverse mapping (if the host name
 * portion of the pathname is truncated or missing, say), requires
 * some smarts elsewhere to weed out duplicate link pathnames.
 */
char *
vtx_to_dir(vp, buf)
	struct vertex *vp;
	char *buf;
{
	strcpy(buf, vp->orig[L_CHANNEL]->name);
	strcat(buf, "/");
	strcat(buf, vp->orig[L_HOST]->name);
	return buf;
}

struct ctlfile *
file_to_ctl(s)
	char *s;
{
	char *file;
	struct spblk *spl;

	if ((file = strrchr(s, '/')) == NULL)
	  file = s;
	else
	  ++file;

	spl = sp_lookup((u_int)atoi(file), spt_mesh[L_CTLFILE]);
	if (spl == NULL)
	  return NULL;
	return (struct ctlfile *)spl->data;
}

void
mklink(from, to)
	char *from, *to;
{
	char *cp;

	if (link(from, to) >= 0)
	  return;
	switch (errno) {
	case ENOENT:	/* this may mean a subdirectory doesn't exist */
	  for (cp = strchr(to, '/'); cp != NULL; cp = strchr(cp+1, '/')) {
	    *cp = '\0';
	    mkdir(to, 0755);
	    *cp = '/';
	  }
	  if (link(from, to) >= 0)
	    return;
	  /* fall through (down to default action) */
	case EEXIST:	/* ignore this, since file may exist previously */
	  if (errno == EEXIST)
	    return;
	  /* fall through (for the sake of second attempt in ENOENT) */
	default:	/* serious problem of some kind */
	  fprintf(stderr, "%s: link(%s, %s): %s\n", progname,
		  from, to, strerror(errno));
	  break;
	}
	return;
}
