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

#include <stdio.h>
#include "hostenv.h"
#include <ctype.h>
#include <sys/stat.h>
#ifdef FCNTL_H
#include FCNTL_H
#endif
#include <sys/file.h>
#include <sysexits.h>
#include <mail.h>
#include "malloc.h"
#include "ta.h"

#ifndef	MAXPATHLEN
#define	MAXPATHLEN 1024
#endif	/* !MAXPATHLEN */

void
ctlclose(dp)
	struct ctldesc *dp;
{
	struct address *ap, *nextap;
	struct rcpt *rp, *nextrp;

	for (rp = dp->recipients; rp != NULL; rp = rp->next) {
		if (rp->lockoffset == 0)
			continue;
		diagnostic(rp, EX_TEMPFAIL, "address was left locked!!");
	}
	if (dp->ctlfd >= 0)
		(void) close(dp->ctlfd);
	if (dp->msgfd >= 0)
		(void) close(dp->msgfd);
	if (dp->contents != NULL)
		free(dp->contents);
	if (dp->offset != NULL)
		free((char *)dp->offset);
	for (ap = dp->senders; ap != NULL; ap = nextap) {
		nextap = ap->link;
		free((char *)ap);
	}
	for (rp = dp->recipients; rp != NULL; rp = nextrp) {
		nextrp = rp->next;
		free((char *)rp);
	}
}


/* as in: SKIPWHILE(isascii,cp) */
#define	SKIPWHILE(X,Y)	while (*Y != '\0' && isascii(*Y) && X(*Y)) { ++Y; }

struct address *
ctladdr(cp)
	char *cp;
{
	struct address *ap;

	ap = (struct address *)emalloc(sizeof (struct address));
	if (ap == NULL)
		return NULL;
	ap->link = NULL;
	SKIPWHILE(isspace,cp);
	ap->channel = cp;
	SKIPWHILE(!isspace,cp);
	*cp++ = '\0';
	SKIPWHILE(isspace,cp);
	ap->host = cp;
	SKIPWHILE(!isspace,cp);
	*cp++ = '\0';
	SKIPWHILE(isspace,cp);
	ap->user = cp;
	/* the user value is allowed to have embedded whitespace */
	while (*cp != '\0')
		++cp;
	--cp;
	while (isascii(*cp) && isdigit(*cp))
		--cp;
	if (isascii(*cp) && *cp == '-')
		--cp;
	ap->misc = cp+1;
	while (isascii(*cp) && isspace(*cp))
		--cp;
	*++cp = '\0';
	return ap;
}

struct ctldesc *
ctlopen(file, channel, host, exitflagp, selectaddr)
	char *file, *channel, *host;
	int *exitflagp;
	int (*selectaddr)();
{
	register char *s;
	char *mfpath;
	int i, n;
	static struct ctldesc d;
	struct address *ap;
	struct rcpt *rp;
	struct stat stbuf;
	extern int markoff(), cistrcmp(), lockaddr();
	extern void warning();
	extern int errno;

	if ((d.ctlfd = open(file, O_RDWR, 0)) < 0) {
		char cwd[MAXPATHLEN], buf[MAXPATHLEN+MAXPATHLEN+100];
		int e = errno;

		(void) getwd(cwd);
		(void) sprintf(buf,
		"Cannot open control file \"%%s\" from \"%s\" as uid %d! (%%m)",
				cwd, geteuid());
		errno = e;
		warning(buf, file);
		return NULL;
	}
	if (fstat(d.ctlfd, &stbuf) < 0) {
		warning("Cannot stat control file \"%s\"! (%m)", file);
		(void) close(d.ctlfd);
		return NULL;
	}
	if ((stbuf.st_mode & S_IFMT) != S_IFREG) {
		warning("Control file \"%s\" is not a regular file!", file);
		(void) close(d.ctlfd);
		return NULL;
	}
	/* 4 is the minimum number of characters per line */
	n = sizeof (long) * (stbuf.st_size / 4);
	if ((d.contents = emalloc((u_int)stbuf.st_size+1)) == NULL
	    || (d.offset = (long *)emalloc((u_int)n)) == NULL) {
		warning("Out of virtual memory!", (char *)NULL);
		exit(EX_SOFTWARE);
	}
	d.contents[stbuf.st_size] = 0; /* Treat it as a long string.. */
	if (read(d.ctlfd, d.contents, stbuf.st_size) != stbuf.st_size) {
		warning("Wrong size read from control file \"%s\"! (%m)",
			       file);
		free(d.contents);
		free((char *)d.offset);
		(void) close(d.ctlfd);
		return NULL;
	}
	n = markoff(d.contents, stbuf.st_size, d.offset, file);
	if (n < 4) {
		/*
		 * If it is less than the minimum possible number of control
		 * lines, then there is something wrong...
		 */
		free(d.contents);
		free((char *)d.offset);
		(void) close(d.ctlfd);
		warning("Truncated or illegal control file \"%s\"!", file);
		/* exit(EX_PROTOCOL); */
		sleep(10);
		return NULL;
	}

	d.ctlid = stbuf.st_ino;
	d.senders = NULL;
	d.recipients = NULL;
	d.logident = "none";

	/* run through the file and set up the information we need */
	for (i = 0; i < n; ++i) {
		if (*exitflagp && d.recipients == NULL)
			break;
		s = d.contents + d.offset[i];
		switch (*s) {
		case _CF_SENDER:
			ap = ctladdr(s+2);
			ap->link = d.senders;
			d.senders = ap;
			break;
		case _CF_RECIPIENT:
			if (*++s != _CFTAG_NORMAL || d.senders == NULL)
				break;
			if ((ap = ctladdr(s+1)) == NULL) {
				warning("Out of virtual memory!", (char *)NULL);
				*exitflagp = 1;
				break;
			}
			if ((channel != NULL &&
				     strcmp(channel, ap->channel) != 0)
			    || (selectaddr != NULL &&
					   !(*selectaddr)(host, ap->host))
			    || (selectaddr == NULL && host != NULL &&
				     cistrcmp(host, ap->host) != 0)
			    || !lockaddr(d.ctlfd, d.offset[i]+1,
						  _CFTAG_NORMAL, _CFTAG_LOCK)) {
				free((char *)ap);
				break;
			}
			ap->link = d.senders;	/* point at sender address */
			rp = (struct rcpt *)emalloc(sizeof (struct rcpt));
			if (rp == NULL) {
				(void) lockaddr(d.ctlfd, d.offset[i]+1,
						 _CFTAG_LOCK, _CFTAG_DEFER);
				warning("Out of virtual memory!", (char *)NULL);
				*exitflagp = 1;
				break;
			}
			rp->addr = ap;
			rp->id = d.offset[i];
			/* XX: XOR locks are different */
			rp->lockoffset = rp->id + 1;
			rp->next = d.recipients;
			rp->desc = &d;
			d.recipients = rp;
			rp->status = EX_OK;
			rp->newmsgheader = NULL;
			break;
		case _CF_MSGHEADERS:
			/* position pointer at start of the header */
			while (*s && *s != '\n')
				++s;
			++s;
			/* fill in header * of recent recipients */
			for (rp = d.recipients;
			     rp != NULL && rp->newmsgheader == NULL;
			     rp = rp->next)
				rp->newmsgheader = s;
			break;
		case _CF_MESSAGEID:
			d.msgfile = s+2;
			break;
		case _CF_BODYOFFSET:
			d.msgbodyoffset = (long)atoi(s+2);
			break;
		case _CF_LOGIDENT:
			d.logident = s+2;
			break;
		}
	}

	if (d.recipients == NULL) {
		ctlclose(&d);
		return NULL;
	}

	mfpath = emalloc((u_int)5+strlen(QUEUEDIR)+strlen(d.msgfile));
	(void) sprintf(mfpath, "../%s/%s", QUEUEDIR, d.msgfile);
	if ((d.msgfd = open(mfpath, O_RDONLY, 0)) < 0) {
		int e = errno;
		for (rp = d.recipients; rp != NULL; rp = rp->next) {
			diagnostic(rp, EX_UNAVAILABLE,
	       "message file is missing(!), you should resend your message");
		}
		errno = e;
		warning("Cannot open message file \"%s\"! (%m)", mfpath);
		free(mfpath);
		ctlclose(&d);
		return NULL;
	}
	return &d;
}

int
ctlsticky(spec_host, addr_host)
	char *spec_host, *addr_host;
{
	static char *hostref = NULL;
	extern int cistrcmp();

	if (hostref == NULL) {
		if (spec_host != NULL)
			hostref = spec_host;
		else
			hostref = addr_host;
	}
	if (spec_host == NULL && addr_host == NULL) {
		hostref = NULL;
		return 0;
	}
	return cistrcmp(hostref, addr_host) == 0;
}
