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

#include "hostenv.h"
#include <stdio.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include "scheduler.h"

extern int rc_command(), rc_expiry(), rc_expform(), rc_group(), rc_interval();
extern int rc_maxchannel(), rc_maxhost(), rc_maxta(), rc_retries(), rc_user();
extern int rc_skew(), rc_byhost(), rc_bychannel(), rc_gangschedule();
extern int rc_deliveryform();

extern char *mailbin;
extern char *progname;
extern char *qdefaultdir;
extern char *replchannel;
extern char *replhost;
extern char *strchr();
extern char *strerror();
extern char *strrchr();
extern int cistrcmp();
extern int errno;
extern int readtoken();
extern int verbose;
extern struct group *getgrnam();
extern struct passwd *getpwnam();

extern void die();
extern void vtxdo();

struct config_entry *default_entry = NULL;


struct rckeyword {
	char	*name;
	int	(*parsef)();
} rckeys[] = {
{	"interval",		rc_interval	},	/* time */
{	"maxchannel",		rc_maxchannel	},	/* number */
{	"maxchannels",		rc_maxchannel	},	/* number */
{	"expiry",		rc_expiry	},	/* time */
{	"command",		rc_command	},	/* array of strings */
{	"expiryform",		rc_expform	},	/* string */
{	"deliveryform",		rc_deliveryform	},	/* string */
{	"retries",		rc_retries	},	/* array of numbers */
{	"maxhost",		rc_maxhost	},	/* number */
{	"maxhosts",		rc_maxhost	},	/* number */
{	"maxta",		rc_maxta	},	/* number */
{	"maxtransport",		rc_maxta	},	/* number */
{	"maxtransports",	rc_maxta	},	/* number */
{	"user",			rc_user		},	/* number */
{	"group",		rc_group	},	/* number */
{	"skew",			rc_skew		},	/* number */
{	"byhost",		rc_byhost	},	/* boolean */
{	"bychannel",		rc_bychannel	},	/* boolean */
{	"gangschedule",		rc_gangschedule	},	/* 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;
	  ce->mark = 0;
	  ce->pending = 0;

	  ce->interval		= defaults->interval;
	  ce->expiry		= defaults->expiry;
	  ce->expiryform	= defaults->expiryform;
	  ce->uid		= defaults->uid;
	  ce->gid		= defaults->gid;
	  ce->command		= defaults->command;
	  ce->bychannel		= defaults->bychannel;
	  ce->byhost		= defaults->byhost;
	  ce->flags		= defaults->flags;
	  ce->maxkids		= defaults->maxkids;
	  ce->maxkidChannel	= defaults->maxkidChannel;
	  ce->maxkidHost	= defaults->maxkidHost;
	  ce->argv		= defaults->argv;
	  ce->nretries		= defaults->nretries;
	  ce->retries		= defaults->retries;
	  ce->skew		= defaults->skew;
	  ce->deliveryform	= defaults->deliveryform;
	} else if (defaults == NULL) {
	  /* Compile these defaults in.. Only for the "*" / "* / *" entry.. */
	  ce->next = NULL;
	  ce->mark = 0;
	  ce->pending = 0;

	  ce->interval = -1;
	  ce->expiry = -1;
	  ce->expiryform = NULL;
	  ce->uid = -1;
	  ce->gid = -1;
	  ce->command = NULL;
	  ce->bychannel = -1;
	  ce->byhost = -1;
	  ce->flags = 0;
	  ce->maxkids = -1;
	  ce->maxkidChannel = -1;
	  ce->maxkidHost = -1;
	  ce->argv = NULL;
	  ce->nretries = 0;
	  ce->retries = NULL;
	  ce->skew = 5;
	  ce->deliveryform = NULL;
	}
}

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

	if (vp->orig[L_CHANNEL] != NULL && vp->orig[L_HOST] != NULL)
		printf("%s/%s",
		       vp->orig[L_CHANNEL]->name, vp->orig[L_HOST]->name);
	else
		printf("%s/%s", ISS(vp->ce.channel), ISS(vp->ce.host));
	printf(" 0x%x\n",(unsigned)ce);
	printf("\tinterval %d\n", ce->interval);
	printf("\texpiry %d\n", ce->expiry);
	printf("\texpiryform %s\n", ISS(ce->expiryform));
	printf("\tdeliveryform %s\n", ISS(ce->deliveryform));
	printf("\tuid %d\n", ce->uid);
	printf("\tgid %d\n", ce->gid);
	printf("\tcommand %s\n", ISS(ce->command));
	printf("\tbychannel %d\n", ce->bychannel);
	printf("\tbyhost %d\n", ce->byhost);
	printf("\tmaxkids %d\n", ce->maxkids);
	printf("\tmaxkidChannel %d\n", ce->maxkidChannel);
	printf("\tmaxkidHost %d\n", ce->maxkidHost);
	if (ce->argv != NULL) {
		for (i = 0; ce->argv[i] != NULL; ++i)
			printf("\targv[%d] = %s\n", i, ce->argv[i]);
	}
	printf("\tnretries %d\n", ce->nretries);
	if (ce->nretries > 0) {
		printf("\tretries = (");
		for (i = 0; i < ce->nretries ; ) {
			printf("%d", ce->retries[i]);
			++i;
			if (i < ce->nretries)
				printf(" ");
		}
		printf(")\n");
	}
	printf("\tskew %d\n", ce->skew);
}

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

	if ((*headp) == NULL)
		(*headp) = (*tailp) = ce;
	else {
		(*tailp)->next = ce;
		(*tailp) = ce;
		for (ce = (*headp); ce != (*tailp); ce = ce->next) {
			if (ce->mark == 0)
				continue;
			ce->mark = 0;
			ce->interval = (*tailp)->interval;
			ce->expiry = (*tailp)->expiry;
			ce->expiryform = (*tailp)->expiryform;
			ce->uid = (*tailp)->uid;
			ce->gid = (*tailp)->gid;
			ce->bychannel = (*tailp)->bychannel;
			ce->byhost = (*tailp)->byhost;
			ce->command = (*tailp)->command;
			ce->maxkids = (*tailp)->maxkids;
			ce->maxkidChannel = (*tailp)->maxkidChannel;
			ce->maxkidHost = (*tailp)->maxkidHost;
			ce->argv = (*tailp)->argv;
			ce->nretries = (*tailp)->nretries;
			ce->retries = (*tailp)->retries;
		}
	}
}

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

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

	if ((fp = fopen(file, "r")) == NULL) {
		fprintf(stderr, "%s: %s: %s\n",
			progname, file, strerror(errno));
		return NULL;
	}
	while ((n = readtoken(fp, line, sizeof line)) != -1) {
		if (verbose)
			printf("read '%s' %d\n",  line, n);
		if (n == 1) {
			if (ce != NULL)
				celink(ce, &head, &tail);
			ce = (struct config_entry *)emalloc(sizeof (struct config_entry));
			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 (line[0]=='*' && (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) {
			if ((cp = strchr(line, '=')) != NULL) {
				*cp = '\0';
				a = cp+1;
				if (*a == '"') {
					++a;
					cp = strrchr(a, '"');
					*cp = '\0';
				}
			} else
				a = NULL;
			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) {
				fprintf(stderr,
					"%s: unknown keyword %s in %s\n",
					progname, line, file);
				++errflag;
			}
		} else {
			fprintf(stderr, "%s: illegal syntax\n", progname);
			++errflag;
		}
	}
	if (ce != NULL)
		celink(ce, &head, &tail);
	fclose(fp);
	if (verbose) {
		v.orig[L_CHANNEL] = v.orig[L_HOST] = NULL;
		for (ce = head; ce != NULL; ce = ce->next) {
			v.ce = *ce;
			vtxprint(&v);
		}
	}
	return errflag ? NULL : head;
}

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

	if (lp == NULL) {
		if (fgets(line, sizeof line, fp) == NULL)
			return -1;
		lp = line;
	}
	while (*lp != '\0' && isspace(*lp))
		++lp;
	if (*lp == '\0' || *lp == '#') {
		lp = NULL;
		return readtoken(fp, buf, buflen);
	}
	for (elp = lp; *elp != '\0'; ++elp) {
		if (isspace(*elp))
			break;
		if (*elp == '=' && *(elp+1) == '"') {
			elp += 2;
			while (*elp != '"' && *elp != '\0') {
				if (*elp == '\\' && *(elp+1) == '\n') {
					if (fgets(elp, sizeof line - (elp - line), fp) == NULL) {
						fprintf(stderr,
						"%s: bad continuation line\n",
							       progname);
						return -1;
					}
				}
				++elp;
			}
			if (*elp == '\0') {
				fprintf(stderr,
					"%s: missing end-quote in: %s\n",
					progname, line);
				return -1;
			}
		}
	}
	strncpy(buf, lp, elp-lp);
	buf[elp-lp] = '\0';
	rv = (lp == line);
	if (*elp == '\0' || *elp == '\n')
		lp = NULL;
	else
		lp = elp;
	return rv;
}

u_int
parse_intvl(string)
	char *string;
{
	u_int	intvl;
	int	val;

	for (intvl = 0; *string != '\0'; ++string) {
		while (isascii(*string) && !isdigit(*string))
			++string;
		val = atoi(string);
		while (isascii(*string) && isdigit(*string))
			++string;
		switch (*string) {
		case 'd':	/* days */
			val *= 24*60*60;
			break;
		case 'h':	/* hours */
			val *= 3600;
			break;
		case 'm':	/* minutes */
			val *= 60;
			break;
		case 's':	/* seconds */
			/* val *= 1; */
			break;
		}
		intvl += val;
	}
	return intvl;
}

struct config_entry *rrcf_head;

int
vtxredo(spl)
        struct spblk *spl;
{
        struct ctlfile *cfp = (struct ctlfile *)spl->data;
	struct vertex *vp;

        /* assert cfp != NULL */
	for (vp = cfp->head ; vp != NULL ; vp = vp->next[L_CTLFILE]) {
		vp->ce.command = NULL;
		vp->ce.argv = NULL;
		vp->ce.retries = NULL;
		vp->ce.nretries = 0;
		vtxdo(vp, rrcf_head);
	}
        return 0;
}

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

	fprintf(stderr,
		"%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]);

	return head;
}

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

	if (*arg != '/') {
		ce->command = emalloc(strlen(mailbin)+1+strlen(qdefaultdir)
				      +1+strlen(arg)+1);
		sprintf(ce->command,
			"%s/%s/%s", mailbin, qdefaultdir, arg);
	} else
		ce->command = strsave(arg);
	j = 0;
	for (cp = ce->command; *cp != '\0' && isascii(*cp);) {
		argv[j++] = cp;
		if (j >= (sizeof argv)/(sizeof argv[0]))
			break;
		while (*cp != '\0' && isascii(*cp) && !isspace(*cp))
			++cp;
		if (*cp == '\0')
			break;
		*cp++ = '\0';
		while (*cp != '\0' && isascii(*cp) && isspace(*cp))
			++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->byhost == -1) {
		for (av = &ce->argv[0]; *av != NULL; ++av)
			if (cistrcmp(*av, replhost) == 0) {
				ce->byhost = 1;
				break;
			}
	}
	if (ce->bychannel == -1) {
		for (av = &ce->argv[0]; *av != NULL; ++av)
			if (cistrcmp(*av, replchannel) == 0) {
				ce->bychannel = 1;
				break;
			}
	}
	return 0;
}

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

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

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

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

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

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

int
rc_maxhost(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->maxkidHost = atoi(arg);
	if (ce->maxkidHost <= 0)
		ce->maxkidHost = 1000;
	return 0;
}

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

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) {
		while (*cp != '\0' && isspace(*cp))
			++cp;
		if (*cp == '\0')
			break;
		d = cp++;
		while (*cp != '\0' && !isspace(*cp))
			++cp;
		c = *cp;
		*cp = '\0';
		i = atoi(d);
		if (i > 0)
			arr[j++] = i;
		else {
			fprintf(stderr,
				"%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 {
		fprintf(stderr, "%s: empty retry factor list\n", progname);
		return 1;
	}
	return 0;
}

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

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

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

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

int
rc_byhost(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->byhost = 1;
	return 0;
}

int
rc_bychannel(key, arg, ce)
	char *key, *arg;
	struct config_entry *ce;
{
	ce->bychannel = 1;
	return 0;
}

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

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