/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */
/*
 *	Lots of modifications (new guts, more or less..) by
 *	Matti Aarnio <mea@nic.funet.fi>  (copyright) 1992-2001
 */

#include "mailer.h"
#include <sfio.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include "scheduler.h"
#include "prototypes.h"
#include "mail.h"
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif

#include "libz.h"
#include "libc.h"

#define SKIPSPACE(Y) while (*Y == ' ' || *Y == '\t' || *Y == '\n') ++Y
#define SKIPTEXT(Y)  while (*Y && *Y != ' ' && *Y != '\t' && *Y != '\n') ++Y
#define SKIPDIGIT(Y) while ('0' <= *Y && *Y <= '9') ++Y

static void celink __((struct config_entry *, struct config_entry **, struct config_entry **, int copy));
static int readtoken __((Sfio_t *fp, char *buf, int buflen, int *linenump));
static int paramparse __((char *line));

#define RCKEYARGS __((char *key, char *arg, struct config_entry *ce))

static int rc_command		RCKEYARGS;
static int rc_expform		RCKEYARGS;
static int rc_expiry		RCKEYARGS;
static int rc_expiry2		RCKEYARGS;
static int rc_group		RCKEYARGS;
static int rc_interval		RCKEYARGS;
static int rc_maxchannel	RCKEYARGS;
static int rc_maxring		RCKEYARGS;
static int rc_maxta		RCKEYARGS;
static int rc_maxthr		RCKEYARGS;
static int rc_idlemax		RCKEYARGS;
static int rc_retries		RCKEYARGS;
static int rc_reporttimes	RCKEYARGS;
static int rc_user		RCKEYARGS;
static int rc_skew		RCKEYARGS;
static int rc_bychannel		RCKEYARGS;
static int rc_ageorder		RCKEYARGS;
static int rc_queueonly		RCKEYARGS;
static int rc_wakeuprestartonly	RCKEYARGS;
static int rc_deliveryform	RCKEYARGS;
static int rc_overfeed		RCKEYARGS;
static int rc_priority		RCKEYARGS;
static int rc_nice		RCKEYARGS;
static int rc_syspriority	RCKEYARGS;
static int rc_sysnice		RCKEYARGS;

extern int errno;

struct config_entry *default_entry = NULL;
struct config_entry *rrcf_head     = NULL;

/* where the  MAILQv2  authentication dataset file is ? */
const char * mq2authfile = NULL;


static struct rckeyword {
	const char	*name;
	int		(*parsef)();
} rckeys[] = {
{	"ageorder",		rc_ageorder	},	/* boolean */
{	"bychannel",		rc_bychannel	},	/* boolean */
{	"command",		rc_command	},	/* array of strings */
{	"deliveryform",		rc_deliveryform	},	/* string */
{	"expiry",		rc_expiry	},	/* time */
{	"expiry2",		rc_expiry2	},	/* time */
{	"expiryform",		rc_expform	},	/* string */
{	"group",		rc_group	},	/* number */
{	"idlemax",		rc_idlemax	},	/* time */
{	"interval",		rc_interval	},	/* time */
{	"maxchannel",		rc_maxchannel	},	/* number */
{	"maxchannels",		rc_maxchannel	},	/* number */
{	"maxring",		rc_maxring	},	/* number */
{	"maxrings",		rc_maxring	},	/* number */
{	"maxta",		rc_maxta	},	/* number */
{	"maxthr",		rc_maxthr	},	/* number */
{	"maxtransport",		rc_maxta	},	/* number */
{	"maxtransports",	rc_maxta	},	/* number */
{	"nice",			rc_nice		},	/* number */
{	"overfeed",		rc_overfeed	},	/* number */
{	"priority",		rc_priority	},	/* number */
{	"queueonly",		rc_queueonly	},	/* boolean */
{	"reporttimes",		rc_reporttimes	},	/* array of numbers */
{	"retries",		rc_retries	},	/* array of numbers */
{	"skew",			rc_skew		},	/* number */
{	"sysnice",		rc_sysnice	},	/* number */
{	"syspriority",		rc_syspriority	},	/* number */
{	"user",			rc_user		},	/* number */
{	"wakeuprestartonly",	rc_wakeuprestartonly },	/* boolean */
{	NULL,			0		}
};


#define ISS(s) ((s)?(s):"<NULL>")
void
defaultconfigentry(ce,defaults)
	struct config_entry *ce, *defaults;
{
	if (defaults && ce != defaults) {
	  /* Use the configurations script defaults.. */
	  ce->next = NULL;
#if 0
	  ce->mark = 0;
#endif

	  ce->interval		= defaults->interval;
	  ce->idlemax		= defaults->idlemax;
	  ce->expiry		= defaults->expiry;
	  ce->expiry2		= defaults->expiry2;
	  ce->expiryform	= defaults->expiryform;
	  ce->uid		= defaults->uid;
	  ce->gid		= defaults->gid;
	  ce->command		= defaults->command;
	  ce->flags		= defaults->flags;
	  ce->maxkids		= defaults->maxkids;
	  ce->maxkidChannel	= defaults->maxkidChannel;
	  ce->maxkidThread	= defaults->maxkidThread;
	  ce->maxkidThreads	= defaults->maxkidThreads;
	  ce->argv		= defaults->argv;
	  ce->nretries		= defaults->nretries;
	  ce->retries		= defaults->retries;
	  ce->reporttimes[0]	= defaults->reporttimes[0];
	  ce->reporttimes[1]	= defaults->reporttimes[1];
	  ce->reporttimes[2]	= defaults->reporttimes[2];
	  ce->reporttimes[3]	= defaults->reporttimes[3];
	  ce->skew		= defaults->skew;
	  ce->deliveryform	= defaults->deliveryform;
	  ce->overfeed		= defaults->overfeed;
	  ce->priority		= defaults->priority;
	} else if (defaults == NULL) {
	  /* Compile these defaults in.. Only for the "*" / "* / *" entry.. */
	  ce->next	= NULL;
#if 0
	  ce->mark	= 0;
#endif

	  ce->interval	= -1;
	  ce->idlemax   = -1;
	  ce->expiry	= -1;
	  ce->expiry2	= -1;
	  ce->expiryform = NULL;
	  ce->uid	= -1;
	  ce->gid	= -1;
	  ce->command	= NULL;
	  ce->flags	= 0;
	  ce->maxkids	= -1;
	  ce->maxkidChannel = -1;
	  ce->maxkidThread  =  1;
	  ce->maxkidThreads = -1;
	  ce->argv	= NULL;
	  ce->nretries	= 0;
	  ce->retries	= NULL;
	  ce->reporttimes[0] = 0;
	  ce->reporttimes[1] = 0;
	  ce->reporttimes[2] = 0;
	  ce->reporttimes[3] = 0;
	  ce->skew	= 5;
	  ce->deliveryform = NULL;
	  ce->overfeed	= 0;
	  ce->priority  = 0; /* nice(0) -- no change */
	}
}

void
vtxprint(vp)
	struct vertex *vp;
{
	int i;
	struct config_entry *ce = &(vp->thgrp->ce);

	if (vp->orig[L_CHANNEL] != NULL && vp->orig[L_HOST] != NULL)
	  sfprintf(sfstdout, "%s/%s", vp->orig[L_CHANNEL]->name,
		   vp->orig[L_HOST]->name);
	else
	  sfprintf(sfstdout, "%s/%s", ISS(ce->channel), ISS(ce->host));
	sfprintf(sfstdout," %p  mark %d\n",	ce, ce->mark);
	sfprintf(sfstdout,"\tinterval %d\n",	(int)ce->interval);
	sfprintf(sfstdout,"\tidlemax %d\n",	ce->idlemax);
	sfprintf(sfstdout,"\texpiry %d\n",	(int)ce->expiry);
	sfprintf(sfstdout,"\texpiry2 %d\n",	(int)ce->expiry2);
	sfprintf(sfstdout,"\texpiryform %s\n",	ISS(ce->expiryform));
	sfprintf(sfstdout,"\tdeliveryform %s\n", ISS(ce->deliveryform));
	sfprintf(sfstdout,"\tuid %d\n",		ce->uid);
	sfprintf(sfstdout,"\tgid %d\n",		ce->gid);
	sfprintf(sfstdout,"\tcommand %s\n",	ISS(ce->command));
	sfprintf(sfstdout,"\tflags:");
	if (ce->flags == 0)
	  sfprintf(sfstdout," (none)");
	else {
	  if (ce->flags & CFG_WITHHOST)  sfprintf(sfstdout," WITHHOST");
	  if (ce->flags & CFG_AGEORDER)  sfprintf(sfstdout," AGEORDER");
	  if (ce->flags & CFG_QUEUEONLY) sfprintf(sfstdout," QUEUEONLY");
	  if (ce->flags & CFG_WAKEUPRESTARTONLY)sfprintf(sfstdout,
							 " WAKEUPRESTARTONLY");
	}
	sfprintf(sfstdout,"\n");
	sfprintf(sfstdout,"\tmaxkids %d\n",		ce->maxkids);
	sfprintf(sfstdout,"\tmaxkidChannel %d\n",	ce->maxkidChannel);
	sfprintf(sfstdout,"\tmaxkidThread  %d\n",	ce->maxkidThread);
	sfprintf(sfstdout,"\tmaxkidThreads %d\n",	ce->maxkidThreads);
	sfprintf(sfstdout,"\toverfeed %d\n",		ce->overfeed);

	if (ce->priority >= 80)
	  sfprintf(sfstdout,"\tpriority %d\n",	ce->priority - 100);
	else
	  sfprintf(sfstdout,"\tnice %d\n",		ce->priority);

	if (ce->argv != NULL) {
	  for (i = 0; ce->argv[i] != NULL; ++i)
	    sfprintf(sfstdout,"\targv[%d] = %s\n", i, ce->argv[i]);
	}

	sfprintf(sfstdout,"\tnretries %d\n", ce->nretries);
	if (ce->nretries > 0) {
	  sfprintf(sfstdout,"\tretries = (");
	  for (i = 0; i < ce->nretries ; ) {
	    sfprintf(sfstdout,"%d", ce->retries[i]);
	    ++i;
	    if (i < ce->nretries)
	      sfprintf(sfstdout," ");
	  }
	  sfprintf(sfstdout,")\n");
	}

	sfprintf(sfstdout,"\treporttimes = (");
	for (i = 0; i < 4 ; ++i ) {
	  sfprintf(sfstdout,"%d", ce->reporttimes[i]);
	  if (i < 3)
	    sfprintf(sfstdout," ");
	}
	sfprintf(sfstdout,")\n");

	sfprintf(sfstdout,"\tskew %d\n", ce->skew);
}

static void
celink(ce, headp, tailp, copy)
	struct config_entry *ce;
	struct config_entry **headp, **tailp;
	int copy;
{
	if (ce == default_entry && *headp != NULL && *tailp != NULL)
	  return; /* XX: ?? */

	if ((*headp) == NULL)
	  (*headp) = (*tailp) = ce;
	else {
	  (*tailp)->next = ce;
	  (*tailp) = ce;

	  if (!copy) return;

	  for (ce = (*headp); ce != (*tailp); ce = ce->next) {

	    if (verbose)
	      sfprintf(sfstdout,"celink() ce = %p  mark=%d\n", ce, ce->mark);

	    if (ce->mark == 0) continue;
	    ce->mark = 0;
	    ce->interval	= (*tailp)->interval;
	    ce->idlemax		= (*tailp)->idlemax;
	    ce->expiry		= (*tailp)->expiry;
	    ce->expiry2		= (*tailp)->expiry2;
	    ce->expiryform	= (*tailp)->expiryform;
	    ce->uid		= (*tailp)->uid;
	    ce->gid		= (*tailp)->gid;
	    ce->command		= (*tailp)->command;
	    ce->flags		= (*tailp)->flags;
	    ce->maxkids		= (*tailp)->maxkids;
	    ce->maxkidChannel	= (*tailp)->maxkidChannel;
	    ce->maxkidThread	= (*tailp)->maxkidThread;
	    ce->maxkidThreads	= (*tailp)->maxkidThreads;
	    ce->argv		= (*tailp)->argv;
	    ce->nretries	= (*tailp)->nretries;
	    ce->retries		= (*tailp)->retries;
	    ce->overfeed	= (*tailp)->overfeed;
	    ce->priority	= (*tailp)->priority;
	  }
	}
}

struct config_entry *
readconfig(file)
	const char *file;
{
	char *cp, *s, *a, line[BUFSIZ];
	int errflag, n;
	struct config_entry *ce, *head, *tail;
	struct rckeyword *rckp;
	struct vertex v;
	Sfio_t *fp;
	int linenum = 0;
	int attrs = 0;

	ce = head = tail = NULL;
	errflag = 0;

	if ((fp = sfopen(NULL, file, "r")) == NULL) {
	  sfprintf(sfstderr, "%s: %s: %s\n",
		   progname, file, strerror(errno));
	  return NULL;
	}
	while ((n = readtoken(fp, line, sizeof line, &linenum)) != -1) {
	  if (verbose)
	    sfprintf(sfstdout, "read '%s' %d\n",  line, n);
	  if (n == 1) {
	    /* Selector entry - or "PARAM" */
	    if (cistrncmp(line,"PARAM",5) == 0) {
	      if (paramparse(line+5)) {
		sfprintf(sfstderr, "%s: illegal syntax at %s:%d\n",
			progname, file, linenum);
		++errflag;
	      }
	      continue;;
	    }

	    if (ce != NULL)
	      celink(ce, &head, &tail, attrs);
	    attrs = 0;
	    ce = (struct config_entry *)emalloc(sizeof (struct config_entry));
	    memset((void*)ce, 0, sizeof(ce));
	    if (verbose) sfprintf(sfstdout,"CE= %p mark=1\n", ce);

	    defaultconfigentry(ce,NULL);
	    ce->mark = 1;
	    if ((s = strchr(line, '/')) != NULL) {
	      *s = 0;
	      ce->channel = strsave(line);
	      *s = '/';
	      ce->host    = strsave(s+1);
	    } else {
	      ce->channel = strsave(line);
	      ce->host    = strsave("*");
	    }
	    if (strcmp(line,"*/*") == 0 || strcmp(line,"*") == 0) {
	      /* The default entry.. */
	      if (default_entry != NULL) {
		if (ce->channel) free (ce->channel);
		if (ce->host) free(ce->host);
		free(ce);
		ce = default_entry;
	      }
	      defaultconfigentry(ce,default_entry);
	      if (default_entry == NULL)
		default_entry = ce;
	    } else
	      defaultconfigentry(ce,default_entry);
	  } else if (ce != NULL) {
	    a = NULL;
	    if ((cp = strchr(line, '=')) != NULL) {
	      char *p = cp-1;
	      *cp = '\0';
	      while (p >= line && (*p == ' ' || *p == '\t'))
		*p-- = '\0';
	      a = cp+1;
	      SKIPSPACE(a);
	      if (*a == '"') {
		++a;
		cp = a;
		while (*cp && *cp != '"') {
		  if (*cp == '\\' && cp[1] != 0)
		    ++cp;
		  ++cp;
		}
		if (*cp)
		  *cp = '\0';
	      }
	    }

	    if (ce && ce->mark) {
	      if (verbose)
		sfprintf(sfstdout," reading entry, ce = %p, mark=0\n", ce);
	      ce->mark = 0;
	    }

	    for (rckp = &rckeys[0]; rckp->name != NULL ; ++rckp)
	      if (cistrcmp(rckp->name, line) == 0) {
		errflag += (*rckp->parsef)(line, a, ce);
		break;
	      }
	    if (rckp->name == NULL) {
	      sfprintf(sfstderr,
		      "%s: unknown keyword %s in %s:%d\n",
		      progname, line, file, linenum);
	      ++errflag;
	    }

	    if (!errflag) attrs = 1;

	  } else {
	    sfprintf(sfstderr, "%s: illegal syntax at %s:%d\n",
		    progname, file, linenum);
	    ++errflag;
	  }
	}
	if (ce != NULL)
	  celink(ce, &head, &tail, 1);
	sfclose(fp);
	if (verbose) {
	  struct threadgroup tg;
	  v.orig[L_CHANNEL] = v.orig[L_HOST] = NULL;
	  v.thgrp = &tg;
	  for (ce = head; ce != NULL; ce = ce->next) {
	    tg.ce = *ce;
	    vtxprint(&v);
	  }
	}
	return errflag ? NULL : head;
}

static int
readtoken(fp, buf, buflen, linenump)
	Sfio_t *fp;
	char *buf;
	int buflen, *linenump;
{
	static char line[BUFSIZ];
	static char *lp = NULL;
	char *elp;
	int rv;

redo_readtoken:
	if (lp == NULL) {
	  if (csfgets(line, sizeof line, fp) < 0)
	    return -1;
	  *linenump += 1;
	  lp = line;
	}
	/* Skip initial white-space */
	SKIPSPACE(lp);
	/* Now it is one of: a token, a comment start, or end of line */
	if (*lp == '\0' || *lp == '#') {
	  /* Comment/EOL */
	  lp = NULL;
	  goto redo_readtoken;
	}
	/* Now we scan for the token + possible value */
	elp = lp;
	while (*elp && *elp != ' ' && *elp != '\t' && *elp != '\n' && *elp != '=' && *elp != '#')
	  ++elp;
	if (isspace(0xFF & *elp)) {
	  /* Allow spaces after the token and before '=' */
	  char *p = elp;
	  SKIPSPACE(p);
	  if (*p == '=')
	    elp = p;
	}
	/* Value indicator ? */
	if (*elp == '=') {
	  /* Allow spaces between '=', and value */
	  ++elp;
	  SKIPSPACE(elp);
	  if (*elp == '"') {
	    ++elp;
	    while (*elp != '"' && *elp != '\0') {
	      if (*elp == '\\' && *(elp+1) == '\n') {
		if (csfgets(elp, sizeof line - (elp - line), fp) < 0) {
		  sfprintf(sfstderr,
			  "%s: bad continuation line\n",
			  progname);
		  return -1;
		}
	      }
	      ++elp;
	    }
	    if (*elp == '\0') {
	      sfprintf(sfstderr,
		      "%s: missing end-quote in: %s\n",
		      progname, line);
	      return -1;
	    }
	    ++elp;
	  } else {
	    SKIPTEXT(elp);
	  }
	}
	strncpy(buf, lp, elp-lp);
	buf[elp-lp] = '\0';
	rv = (lp == line);
	if (*elp == '\0' || *elp == '\n')
	  lp = NULL;
	else
	  lp = elp;
	return rv;
}


struct config_entry *
rereadconfig(head, file)
	struct config_entry *head;
	const char *file;
{
	struct config_entry *ce, *nce, *head2;

	sfprintf(sfstderr,
		"%s: reread configuration file: %s\n", progname, file);
	/* free all the old config file entries */
	for (ce = head; ce != NULL; ce = nce) {
	  nce = ce->next;
	  /* Process the  default_entry  cleanup as the LAST one */
	  if (ce == default_entry)
	    continue;
	  /* Free up all malloc()ed blocks */
	  if (ce->command != NULL &&
	      (default_entry == NULL ||
	       ce->command != default_entry->command))
	    free(ce->command);
	  if (ce->argv != NULL &&
	      (default_entry == NULL ||
	       ce->argv != default_entry->argv))
	    free((char *)ce->argv);
	  if (ce->retries != NULL && ce->nretries > 0 &&
	      (default_entry == NULL ||
	       ce->retries != default_entry->retries))
	    free((char *)ce->retries);
	  free((char *)ce);

	  /* Process the  default_entry  cleanup as the LAST one */
	  if (nce == NULL) {
	    nce = default_entry;
	    if (nce != NULL)
	      nce->next = NULL; /* It no longer has any followers.. */
	    default_entry = NULL;
	  }
	}

	/* read the new stuff in */
	if ((head2 = readconfig(file)) == NULL) {
	  char *cp = emalloc(strlen(file)+50);
	  sprintf(cp, "null control file: %s", file);
	  die(1, cp);
	  /* NOTREACHED */
	}

	/* apply it to all the existing vertices */
	rrcf_head = head2;
	sp_scan(vtxredo, (struct spblk *)NULL, spt_mesh[L_CTLFILE]);

	endpwent(); /* Close the databases */
	endgrent();

	return head;
}

static int rc_command(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	char *cp, **av, *argv[100];
	int j;

	ce->command = strsave(arg);
	j = 0;
	for (cp = ce->command; *cp;) {
	  argv[j++] = cp;
	  if (j >= (sizeof argv)/(sizeof argv[0]))
	    break;
	  SKIPTEXT(cp);
	  if (*cp == '\0')  break;
	  *cp++ = '\0';
	  SKIPSPACE(cp);
	}
	argv[j++] = NULL;
	if (j > 0) {
	  ce->argv = (char **)emalloc(sizeof (char *) * j);
	  memcpy((char *)ce->argv, (char *)&argv[0], sizeof (char *) * j);
	}
	if (!(ce->flags & CFG_WITHHOST)) {
	  for (av = &ce->argv[0]; *av != NULL; ++av)
	    if (strcmp(*av, replhost) == 0) {
	      ce->flags |= CFG_WITHHOST;
	      break;
	    }
	}

	/* ``replchannel'' need not matched, idle processing
	   takes it properly into account. */

	return 0;
}

static int rc_expform(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->expiryform = strsave(arg);
	return 0;
}

static int rc_expiry(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->expiry = parse_interval(arg,NULL);
	return 0;
}

static int rc_expiry2(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->expiry2 = parse_interval(arg,NULL);
	return 0;
}

static int rc_group(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	struct Zgroup *gr;

	if (isascii(*arg) && isdigit(*arg))
	  ce->gid = atoi(arg);
	else if ((gr = zgetgrnam(arg)) == NULL) {
	  sfprintf(sfstderr, "%s: unknown group: '%s'\n", progname, arg);
	  return 1;
	} else
	  ce->gid = gr->gr_gid;
	return 0;
}

static int rc_interval(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->interval = parse_interval(arg,NULL);
	return 0;
}

static int rc_idlemax(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->idlemax = parse_interval(arg,NULL);
	return 0;
}

static int rc_overfeed(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->overfeed = atoi(arg);
	if (ce->overfeed < 0)
	  ce->overfeed = 0;
	return 0;
}

static int rc_priority(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	if (sscanf(arg,"%d",&ce->priority) != 1 ||
	    ce->priority < -20 || ce->priority > 19) {
	  sfprintf(sfstderr, "%s: Bad UNIX priority value, acceptable in range: -20..19; input=\"%s\"\n", progname, arg);
	  return 1;
	}
	ce->priority += 100;
	return 0;
}

static int rc_nice(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	if (sscanf(arg,"%d",&ce->priority) != 1 ||
	    ce->priority < -40 || ce->priority > 39) {
	  sfprintf(sfstderr, "%s: Bad UNIX nice offset value, acceptable in range: -40..39; input=\"%s\"\n", progname, arg);
	  return 1;
	}
	return 0;
}

static int rc_syspriority(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	int i;
	if (sscanf(arg,"%d",&i) != 1 ||
	    i < -20 || i > 19) {
	  sfprintf(sfstderr, "%s: Bad UNIX priority value, acceptable in range: -20..19; input=\"%s\"\n", progname, arg);
	  return 1;
	}
#if defined(HAVE_SETPRIORITY) && defined(HAVE_SYS_RESOURCE_H)
	/* PRIO_PROCESS depends likely of  HAVE_SYS_RESOURCE_H */
	setpriority(PRIO_PROCESS, 0, i);
#endif
	return 0;
}

static int rc_sysnice(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	int i;
	if (sscanf(arg,"%d",&i) != 1 ||
	    i < -40 || i > 39) {
	  sfprintf(sfstderr, "%s: Bad UNIX nice offset value, acceptable in range: -40..39; input=\"%s\"\n", progname, arg);
	  return 1;
	}
	nice(i);
	return 0;
}

static int rc_maxchannel(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->maxkidChannel = atoi(arg);
	if (ce->maxkidChannel <= 0)
	  ce->maxkidChannel = 10000;
	return 0;
}

static int rc_maxring(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->maxkidThreads = atoi(arg);
	if (ce->maxkidThreads <= 0)
	  ce->maxkidThreads = 10000;
	return 0;
}

static int rc_maxta(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->maxkids = atoi(arg);
	if (ce->maxkids <= 0)
	  ce->maxkids = 10000;
	if (ce->maxkids > global_maxkids)
	  ce->maxkids = global_maxkids;
	return 0;
}

static int rc_maxthr(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->maxkidThread = atoi(arg);
	if (ce->maxkidThread <= 0)
	  ce->maxkidThread = 1;
	return 0;
}

static int rc_retries(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	int i, j, arr[100];
	char c, *cp, *d;

	j = 0;
	for (cp = arg; *cp != '\0'; ++cp) {
	  SKIPSPACE(cp);
	  if (*cp == '\0')
	    break;
	  d = cp++;
	  SKIPTEXT(cp);
	  c = *cp;
	  *cp = '\0';
	  i = atoi(d);
	  if (i > 0)
	    arr[j++] = i;
	  else {
	    sfprintf(sfstderr,
		    "%s: not a numeric factor: %s\n",
		    progname, d);
	    return 1;
	  }
	  if (j >= (sizeof arr)/(sizeof arr[0]))
	    break;
	  *cp = c;
	  if (*cp == '\0')
	    break;
	}
	if (j > 0) {
	  ce->retries = (int *)emalloc((u_int)(sizeof (int) * j));
	  memcpy((char *)ce->retries, (char *)&arr[0], sizeof (int) * j);
	  ce->nretries = j;
	} else {
	  sfprintf(sfstderr, "%s: empty retry factor list\n", progname);
	  return 1;
	}
	return 0;
}

static int rc_reporttimes(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	int i, j, arr[_CFTAG_RCPTDELAYSIZE];
	char c, *cp, *d;

	for (j = 0; j < sizeof(arr)/(sizeof(arr[0])); ++j)  arr[j] = 0;

	j = 0;
	for (cp = arg; *cp != '\0'; ++cp) {
	  SKIPSPACE(cp);
	  if (*cp == '\0')
	    break;
	  d = cp++;
	  SKIPTEXT(cp);
	  c = *cp;
	  *cp = '\0';
	  i = parse_interval(d, NULL);
	  if (i > 0)
	    arr[j++] = i;
	  else {
	    sfprintf(sfstderr,
		    "%s: not a numeric factor: %s\n",
		    progname, d);
	    return 1;
	  }
	  if (j >= (sizeof arr)/(sizeof arr[0]))
	    break;
	  *cp = c;
	  if (*cp == '\0')
	    break;
	}

	for (j = 0; j < sizeof(arr)/(sizeof(arr[0])); ++j)
	  ce->reporttimes[j] = arr[j];

	return 0;
}

static int rc_user(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	struct Zpasswd *pw;

	if (isascii(*arg) && isdigit(*arg))
	  ce->uid = atoi(arg);
	else if ((pw = zgetpwnam(arg)) == NULL) {
	  sfprintf(sfstderr, "%s: unknown user: %s\n", progname, arg);
	  return 1;
	} else
	  ce->uid = pw->pw_uid;
	return 0;
}

static int rc_skew(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	int v;

	if (!isascii(*arg) || !isdigit(*arg) || (v = atoi(arg)) < 1) {
	  sfprintf(sfstderr, "%s: bad skew value: %s\n", progname, arg);
	  return 1;
	}
	ce->skew = v;
	return 0;
}

static int rc_bychannel(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	return 0;
}

static int rc_ageorder(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->flags |= CFG_AGEORDER;
	return 0;
}

static int rc_deliveryform(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->deliveryform = strsave(arg);
	return 0;
}

static int rc_queueonly(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->flags |= CFG_QUEUEONLY;
	return 0;
}

static int rc_wakeuprestartonly(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->flags |= CFG_WAKEUPRESTARTONLY;
	return 0;
}

extern int mailqmode;

char *zenvexpand(line)
     char *line;
{
	char *s = line;
	char *v;
	const char *e;
	char end_c;
	int spotlen;
	int e_len;

	if (!line) return NULL;

	for (;*s;++s) {
	  if (*s == '$') {
	    if (s[1] == '{') {
	      v = s;
	      for (;*s && *s != '}'; ++s) ;
	      end_c = *s;
	      *s = 0;
	      spotlen = s - v + 1;
	      e = getzenv(v+2);
	      *s = end_c;
	      if (!e) continue; /* No such zenv-variable :-/ */
	      e_len = strlen(e);
	      if (e_len > spotlen) {
		/* Must expand a bit */
		char *n = malloc(strlen(line)+e_len-spotlen+2);
		int p = v - line;
		if (!n) continue; /* alloc failure */
		if (p > 0)
		  memcpy(n, line, p);
		s = n + p;
		memcpy(s, e, e_len);
		s += e_len;
		p += spotlen;
		strcpy(s, line + p); /* Tail */
		free(line);
		line = n;
	      } else {
		/* New data has same or smaller size */
		memcpy(v, e, e_len);
		if (e_len < spotlen) /* Smaller size */
		  strcpy(v + e_len, v + spotlen);
		v += e_len;
		s = v;
	      }
	    }
	  }
	}
	return line;
}


static int paramparse(line)
	char *line;
{
	char *s, *a = NULL;

	if ((s = strchr(line, '=')) != NULL) {
	  char *p = s-1;
	  *s = '\0';
	  while (p >= line && (*p == ' ' || *p == '\t'))
	    *p-- = '\0';
	  a = s+1;
	  SKIPSPACE(a);
	  if (*a == '"') {
	    ++a;
	    s = a;
	    while (*s && *s != '"') {
	      if (*s == '\\' && s[1] != 0)
		++s;
	      ++s;
	    }
	    if (*s)
	      *s = '\0';
	  }
	}

	if (cistrcmp(line,"authfile")==0 && a) {
	  if (mq2authfile)
	    free((void*)mq2authfile);
	  mq2authfile = zenvexpand(strsave(a));

	  if (mq2authfile && access(mq2authfile,R_OK)==0)
	    mailqmode = 2;

	  return 0;
	}

	if (cistrcmp(line,"mailqsock")==0 && a) {
	  if (mailqsock)
	    free((void*)mailqsock);
	  mailqsock = zenvexpand(strsave(a));
	  return 0;
	}

	if (cistrcmp(line,"msgwriteasync")==0 && a) {
	  msgwriteasync = atoi(s);
	  return 0;
	}

	if (cistrcmp(line,"notifysock")==0 && a) {
	  if (notifysock)
	    free((void*)notifysock);
	  notifysock = zenvexpand(strsave(a));
	  return 0;
	}

	if (cistrcmp(line,"global-report-interval")==0 && a) {
	  global_report_interval = parse_interval(a, NULL);
	  return 0;
	}

	return 1;
}
