/*
 *	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 <signal.h>
#include <sysexits.h>
#include <varargs.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <mail.h>
#include "ta.h"
#ifdef	USE_UNIONWAIT
#include <sys/wait.h>
#endif	/* USE_UNIONWAIT */

#ifndef	L_SET
#define	L_SET	0
#endif	/* !L_SET */

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

#define	FROM_	"From "

#ifndef	MAXHOSTNAMELEN
#define	MAXHOSTNAMELEN 64
#endif	/* MAXHOSTNAMELEN */

char uucpname[MAXHOSTNAMELEN+1];

char *progname;
int readalready = 0;		/* does buffer contain valid message data? */
FILE *logfp;

extern void warning();
extern char *emalloc(), *erealloc();

struct maildesc {
	char	*name;
	short	flags;
	char	*command;
	char	*argv[20];
};

#define	MO_FFROMFLAG		   01
#define	MO_RFROMFLAG		   02
#define	MO_NORESETUID		   04
#define	MO_STRIPQUOTES		  010
#define	MO_MANYUSERS		  020
#define	MO_RETURNPATH		  040
#define	MO_UNIXFROM		 0100
#define	MO_HIDDENDOT		 0200
#define	MO_ESCAPEFROM		 0400
#define	MO_STRIPHIBIT		01000
#define	MO_REMOTEFROM		02000
#define	MO_CRLF			04000

struct exmapinfo {
	int	origstatus;
	char	*statusmsg;
	int	newstatus;
} exmap[] = {
{	EX_USAGE,	"command line usage error",	EX_TEMPFAIL	},
{	EX_DATAERR,	"data format error",		EX_DATAERR	},
{	EX_NOINPUT,	"cannot open input",		EX_TEMPFAIL	},
{	EX_NOUSER,	"addressee unknown",		EX_NOUSER	},
{	EX_NOHOST,	"host name unknown",		EX_NOHOST	},
{	EX_UNAVAILABLE,	"service unavailable",		EX_UNAVAILABLE	},
{	EX_SOFTWARE,	"internal software error",	EX_TEMPFAIL	},
{	EX_OSERR,	"system error",			EX_TEMPFAIL	},
{	EX_OSFILE,	"critical OS file missing",	EX_TEMPFAIL	},
{	EX_CANTCREAT,	"can't create output file",	EX_TEMPFAIL	},
{	EX_IOERR,	"input/output error",		EX_TEMPFAIL	},
{	EX_TEMPFAIL,	"temporary failure",		EX_TEMPFAIL	},
{	EX_PROTOCOL,	"remote error in protocol",	EX_TEMPFAIL	},
{	EX_NOPERM,	"permission denied",		EX_NOPERM	},
{	0,		NULL,				EX_TEMPFAIL	}
};

#ifdef	lint
#undef	putc
#define	putc	fputc
#endif	/* lint */

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

int
main(argc, argv)
	int argc;
	char *argv[];
{
	char file[MAXPATHLEN+1];
	char *channel, *host, *mailer, *cf;
	struct ctldesc *dp;
	int errflg, c;
	struct maildesc *mp;
	extern char *optarg;
	extern int optind;
	extern int getmyuucpname(), emptyline();
	extern struct maildesc *readsmcf();
	extern void prversion(), process();

	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
		(void) signal(SIGINT, wantout);
	if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
		(void) signal(SIGHUP, wantout);
	if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
		(void) signal(SIGTERM, wantout);
	(void) signal(SIGPIPE, SIG_IGN);

	if ((progname = strrchr(argv[0], '/')) == NULL)
		progname = argv[0];
	else
		++progname;
	errflg = 0;
	host = channel = NULL;
	cf = NULL;
	while ((c = getopt(argc, argv, "f:c:h:V")) != EOF) {
		switch (c) {
		case 'f':
			cf = optarg;
			break;
		case 'c':	/* remote hostname */
			channel = optarg;
			break;
		case 'h':	/* remote hostname */
			host = optarg;
			break;
		case 'V':
			prversion("sm");
			exit(0);
			break;
		default:
			++errflg;
			break;
		}
	}
	if (errflg || optind != argc - 1 || host == channel) {
		(void) fprintf(stderr,
		         "Usage: %s [-c channel || -h host] mailer\n", argv[0]);
		exit(EX_USAGE);
	}
	mailer = argv[optind];

	if ((mp = readsmcf(cf, mailer)) == NULL)
		exit(EX_OSFILE);
	if (mp->flags & MO_REMOTEFROM)
		(void) getmyuucpname(uucpname, sizeof uucpname);	/*XX*/
	while (!getout && fgets(file, sizeof file, stdin)) {
		if (emptyline(file, sizeof file))
			break;
		(void) ctlsticky((char *)NULL, (char *)NULL);	/* reset */
		dp = ctlopen(file, channel, host, &getout, ctlsticky);
		if (dp == NULL)
			continue;
		process(dp, mp);
		ctlclose(dp);
	}
	if (logfp != NULL)
		(void) fclose(logfp);
	exit(EX_OK);
	/* NOTREACHED */
	return 0;
}

void
process(dp, mp)
	struct ctldesc *dp;
	struct maildesc *mp;
{
	struct rcpt *rp, *rphead;
	extern void process(), deliver();

	readalready = 0; /* ignore any previous message data cache */

	if (mp->flags & MO_MANYUSERS) {
		for (rp = rphead = dp->recipients; rp != NULL; rp = rp->next) {
			if (rp->next == NULL
			    || rp->addr->link != rp->next->addr->link
			    || rp->newmsgheader != rp->next->newmsgheader) {
				deliver(mp, rphead, rp->next,
					    dp->msgfd, dp->msgbodyoffset);
				rphead = rp->next;
			}
		}
	} else {
		for (rp = dp->recipients; rp != NULL; rp = rp->next)
			deliver(mp, rp, rp->next, dp->msgfd, dp->msgbodyoffset);
	}
}

/*
 * deliver - deliver the letter in to user's mail box.  Return
 *	     errors and requests for further processing in the structure
 */

void
deliver(mp, startrp, endrp, messagefd, msgoffset)
	struct maildesc *mp;
	struct rcpt *startrp, *endrp;
	int messagefd;
	long msgoffset;
{
	struct rcpt *rp;
	struct exmapinfo *exp;
	int i, j, pid, in[2], out[2];
	unsigned int avsize;
	FILE *tafp, *errfp;
	char *cp, *dp, *ds, *s, buf[BUFSIZ], buf2[BUFSIZ];
	char **av;
#ifdef	USE_UNIONWAIT
	union wait status;
#else	/* !USE_UNIONWAIT */
	int	status;
#endif	/* USE_UNIONWAIT */
	extern int appendlet();

	if (lseek(messagefd, msgoffset, L_SET) < 0L)
		warning("Cannot seek to message body! (%m)", (char *)NULL);
	i = 0;
	avsize = 5;
	av = (char **)emalloc(sizeof av[0] * avsize);
	av[i++] = mp->argv[0];
	if (mp->flags & MO_FFROMFLAG) {
		av[i++] = "-f";
		av[i++] = startrp->addr->link->user;
	} else if (mp->flags & MO_RFROMFLAG) {
		av[i++] = "-r";
		av[i++] = startrp->addr->link->user;
	}
	for (j = 1; mp->argv[j] != NULL; ++j) {
		while (i+2 >= avsize) {
			avsize *= 2;
			av = (char **)erealloc((char *)av,
					       sizeof av[0] * avsize);
		}
		if (strchr(mp->argv[j], '$') == 0) {
			av[i++] = mp->argv[j];
			continue;
		}
		rp = startrp;
		do {
			while (i+2 >= avsize) {
				avsize *= 2;
				av = (char **)erealloc((char *)av,
						       sizeof av[0] * avsize);
			}
			for (cp = mp->argv[j], s = buf; *cp != '\0'; ++cp) {
				if (*cp == '$') {
					switch (*++cp) {
					case 'g':
						ds = rp->addr->link->user;
						break;
					case 'h':
						ds = rp->addr->host;
						break;
					case 'u':
						ds = rp->addr->user;
						rp = rp->next;
						break;
					case 'U':
						strcpy(buf2, rp->addr->user);
						for (dp = buf2; *dp != 0; ++dp)
							if (isupper(*dp))
								*dp = tolower(*dp);
						ds = buf2;
						rp = rp->next;
						break;
					default:
						ds = NULL;
						break;
					}
					if (ds == NULL || *ds == '\0') {
						char msg[BUFSIZ];

						(void) sprintf(msg,
						    "Null value for $%c (%%s)!",
						       *cp);
						warning(msg, mp->name);
					} else {
						(void) strcpy(s, ds);
						s += strlen(s);
					}
				} else
					*s++ = *cp;
			}
			*s = '\0';
			av[i] = emalloc((u_int)(strlen(buf)+1));
			/* not worth freeing this stuff */
			(void) strcpy(av[i], buf);
			++i;
		} while (rp != startrp && rp != endrp);
	}
	av[i] = NULL;
	/* now we can fork off and run the command... */
	if (pipe(in) < 0) {
		for (rp = startrp; rp != endrp; rp = rp->next)
			diagnostic(rp, EX_OSERR,
				       "cannot create pipe from \"%s\"",
				       mp->command);
		return;
	}
	if (pipe(out) < 0) {
		for (rp = startrp; rp != endrp; rp = rp->next)
			diagnostic(rp, EX_OSERR,
				       "cannot create pipe to \"%s\"",
				       mp->command);
		return;
	}
	if ((pid = fork()) == 0) {	/* child, run the command */
		(void) close(in[0]);
		(void) close(out[1]);
		if (!(mp->flags & MO_NORESETUID))
			(void) setuid(getuid());
		/* its stdout and stderr is the pipe, its stdin is our tafp */
		(void) close(0);
		(void) close(1);
		(void) close(2);
		(void) dup2(out[0], 0);
		(void) close(out[0]);
		(void) dup2(in[1], 1);
		(void) dup2(in[1], 2);
		(void) close(in[1]);
		(void) execv(mp->command, av);
		_exit(1);
	} else if (pid < 0) {		/* couldn't fork, complain */
		for (rp = startrp; rp != endrp; rp = rp->next)
			diagnostic(rp, EX_OSERR, "cannot fork");
		return;
	}
	(void) close(out[0]);
	(void) close(in[1]);
	tafp = fdopen(out[1], "w");
	errfp = fdopen(in[0], "r");
	/* read any messages from its stdout/err on in[0] */
#ifdef	notdef
	(void) printf("%s\n\t", mp->command);
	for (i = 0; av[i] != NULL; ++i);
		(void) printf("%s ", av[i]);
	(void) printf("\n");
#endif
	free((char *)av);
	/* ... having forked and set up the pipe, we quickly continue */
	if (mp->flags & (MO_UNIXFROM|MO_REMOTEFROM)) {
		char *timestring;
		time_t now;
		extern time_t time();

		now = time((time_t *)0);
		timestring = ctime(&now);
		*(timestring+strlen(timestring)-1) = '\0';
		(void) fprintf(tafp, "%s%s %s", FROM_,
				     startrp->addr->link->user, timestring);
		if (mp->flags & MO_REMOTEFROM)
			(void) fprintf(tafp, " remote from %s", uucpname);
		(void) putc('\n', tafp);
	}
	(void) fwrite(startrp->newmsgheader,
		      sizeof (char), strlen(startrp->newmsgheader), tafp);
	/* XX: what if we already have a Return-Path: ? */
	if (mp->flags & MO_RETURNPATH)
		(void) fprintf(tafp, "Return-Path: <%s>\n",
				     startrp->addr->link->user);
	(void) fputc('\n', tafp);
	/* append message body itself */
	if ((i = appendlet(mp, tafp, messagefd)) != EX_OK) {
		for (rp = startrp; rp != endrp; rp = rp->next)
			diagnostic(rp, i, "write error");
		/* just to make sure nothing will get delivered */
		(void) kill(pid, SIGTERM);
		(void) sleep(1);
		(void) kill(pid, SIGKILL);
		(void) wait((union wait *)0);
		return;
	}
	(void) fclose(tafp);
	(void) close(out[1]);	/* paranoia */
	if (fgets(buf, sizeof buf, errfp) == NULL)
		buf[0] = '\0';
	else if ((cp = strchr(buf, '\n')) != NULL)
		*cp = '\0';
	(void) fclose(errfp);
	(void) close(in[0]);	/* more paranoia */
	pid = wait(&status);
	cp = buf + strlen(buf);
#ifdef	USE_UNIONWAIT
	if (status.w_termsig) {
		if (cp != buf)
			*cp++ = ' ';
		sprintf(cp, "[signal %d", status.w_termsig);
		if (status.w_coredump)
			(void) strcat(cp, " (Core dumped)");
		(void) strcat(cp, "]");
		i = EX_TEMPFAIL;
	} else if (status.w_retcode == 0 || status.w_retcode == EX_OK) {
		i = EX_OK;
	} else {
		i = status.w_retcode;
#else	/* !USE_UNIONWAIT */
	if (status&0177) {
		if (cp != buf)
			*cp++ = ' ';
		sprintf(cp, "[signal %d", status&0177);
		if (status&0200)
			(void) strcat(cp, " (Core dumped)");
		(void) strcat(cp, "]");
		i = EX_TEMPFAIL;
	} else if (((status>>8)&0377) == 0 || ((status>>8)&0377) == EX_OK) {
		i = EX_OK;
	} else {
		i = (status>>8)&0377;
#endif	/* USE_UNIONWAIT */
		for (exp = exmap; exp->origstatus != 0; ++exp)
			if (exp->origstatus == i)
				break;

		s = exp->statusmsg;
		i = exp->newstatus;
#ifdef	USE_UNIONWAIT
		sprintf(cp, "[exit status %d", status.w_retcode);
#else	/* !USE_UNIONWAIT */
		sprintf(cp, "[exit status %d", ((status>>8)&0377));
#endif	/* USE_UNIONWAIT */
		if (s)
			sprintf(cp+strlen(cp), " (%s)", s);
		(void) strcat(cp, "]");
	}
	for (rp = startrp; rp != endrp; rp = rp->next)
		diagnostic(rp, i, "%s", buf);
	/* XX: still need to deal with MO_STRIPQUOTES */
}

/*
 * appendlet - append letter to file pointed at by fd
 */

int
appendlet(mp, fp, mfd)
	struct maildesc *mp;
	FILE *fp;
	int mfd;
{
	register int i;
	register int bufferfull;
	int lastwasnl;
	static char buffer[BUFSIZ];
	extern int writebuf();

	/* can we use cache of message body data */
	if (readalready != 0) {
		if (writebuf(mp, fp, buffer, readalready) != readalready)
			return EX_IOERR;
		if (buffer[readalready-1] != '\n')
			(void) writebuf(mp, fp, "\n", 1);
		return EX_OK;
	}

	/* we are assumed to be positioned properly at start of message body */
	bufferfull = lastwasnl = 0;
	(void) writebuf(mp, fp, (char *)NULL, 0);  /* magic initialization */
	while ((i = read(mfd, buffer, sizeof buffer)) != 0) {
		if (i < 0)
			return EX_IOERR;
		lastwasnl = (buffer[i-1] == '\n');
		if (writebuf(mp, fp, buffer, i) != i)
			return EX_IOERR;
		readalready = i;
		bufferfull++;
	}
	/* we must make sure the last thing we transmit is a CRLF sequence */
	if (!lastwasnl)
		(void) writebuf(mp, fp, "\n", 1);

	if (bufferfull > 1)	/* not all in memory, need to reread */
		readalready = 0;
	return EX_OK;
}

/*
 * Writebuf() is like write(), except all '\n' are converted to "\r\n"
 * (CRLF), and the sequence "\n.\n" is converted to "\r\n..\r\n".
 */

int
writebuf(mp, fp, buf, len)
	struct maildesc *mp;
	FILE *fp;
	char *buf;
	int len;
{
	register char *cp;
	register int n;
	int tlen;
	register char expect;
	static char save = '\0';

	if (buf == NULL) {		/* magic initialization */
		save = '.';
		return 0;
	}
	expect = save;
	if (mp->flags & MO_STRIPHIBIT) {
		for (cp = buf, n = len; n > 0; --n, ++cp)
			if (*cp & 0200)
				*cp &= 0177;
	}
	for (cp = buf, n = len, tlen = 0; n > 0; --n, ++cp) {
		if (*cp == '\n') {
			if (cp > buf)
				tlen += fwrite(buf, sizeof (char), cp-buf, fp);
			if (expect == '\n' && (mp->flags & MO_HIDDENDOT))
				/* "\n.\n" sequence */
				(void) putc('.', fp);
			if (mp->flags & MO_CRLF)
				(void) putc('\r', fp);
			buf = cp;	/* write out the \n next time around */
			expect = '.';
		} else if (expect != '\0') {
			switch (expect) {
			case 'r':
				if (*cp == expect) expect = 'o';
				else /* expect = '\0'; */
			case 'o':
				if (*cp == expect) expect = 'm';
				else /* expect = '\0'; */
			case 'm':
				if (*cp == expect) expect = ' ';
				else /* expect = '\0'; */
			case ' ':
				if (*cp == expect) {
					if (*buf == '\n')
						++tlen, (void) putc('\n', fp);
					(void) putc('>', fp);
					if (cp-4 >= buf)
						buf = cp-4/* see mailbox.c */;
					expect = '\0';
				} else
			case '.':
				if (*cp == '.') {
					expect = '\n';
					break;
				} else if ((mp->flags & MO_ESCAPEFROM)
					   && *cp == 'F') {
					expect = 'r';
					break;
				} else
				/* FALL THROUGH */
			default:
				expect = '\0';
			}
		}
	}
	save = expect;
	if (cp > buf)
		tlen += fwrite(buf, sizeof (char), cp-buf, fp);
	return tlen;
}

struct maildesc *
readsmcf(file, mailer)
	char *file, *mailer;
{
	char *cp, *entry, buf[BUFSIZ];
	FILE *fp;
	int i;
	static struct maildesc m;
	extern char *emalloc();

	if (file == NULL) {
		char *mailshare = getzenv("MAILSHARE");

		if (mailshare == NULL)
			mailshare = MAILSHARE;
		(void) sprintf(buf, "%s/%s.conf", mailshare, progname);
		file = buf;
	}
	if ((fp = fopen(file, "r")) == NULL) {
		(void) fprintf(stderr, "%s: cannot open ", progname);
		perror(file);
		exit(EX_OSFILE);
	}
	entry = cp = NULL;
	while (fgets(buf, sizeof buf, fp) != NULL) {
		if (buf[0] == '#' || buf[0] == '\n')
			continue;
		if ((cp = emalloc((u_int)(strlen(buf)+1))) == NULL) {
			(void) fprintf(stderr, "%s: Out of Virtual Memory!\n",
					       progname);
			exit(EX_OSERR);
		}
		entry = cp;
		(void) strcpy(entry, buf);
		SKIPWHILE(!isspace, cp);
		if (isascii(*cp) && isspace(*cp)) {
			if (*cp == '\n') {
				(void) fprintf(stderr, "%s: %s: bad entry: %s",
						       progname, file, entry);
			} else
				*cp = '\0';
		} else {
			(void) fprintf(stderr, "%s: %s: bad entry: %s",
					       progname, file, entry);
		}
		if (strcmp(entry, mailer) == 0)
			break;
		free(entry);
		entry = NULL;
	}
	(void) fclose(fp);
	if (entry == NULL)
		return NULL;
	m.name = entry;
	m.flags = MO_UNIXFROM;
	++cp;
	SKIPWHILE(isspace, cp);
	/* process mailer option flags */
	for (;isascii(*cp) && !isspace(*cp); ++cp) {
		switch (*cp) {
		case 'f':	m.flags |= MO_FFROMFLAG;	break;
		case 'r':	m.flags |= MO_RFROMFLAG;	break;
		case 'S':	m.flags |= MO_NORESETUID;	break;
		case 'n':	m.flags &= ~MO_UNIXFROM;	break;
		case 's':	m.flags |= MO_STRIPQUOTES;	break;
		case 'm':	m.flags |= MO_MANYUSERS;	break;
		case 'P':	m.flags |= MO_RETURNPATH;	break;
		case 'U':	m.flags |= MO_REMOTEFROM;	break;
		case 'X':	m.flags |= MO_HIDDENDOT;	break;
		case 'E':	m.flags |= MO_ESCAPEFROM;	break;
		case '7':	m.flags |= MO_STRIPHIBIT;	break;
		case 'l':	/* this is a local mailer */
		case 'F':	/* this mailer wants a From: line */
		case 'D':	/* this mailer wants a Date: line */
		case 'M':	/* this mailer wants a Message-Id: line */
		case 'p':	/* use SMTP return path */
		case 'x':	/* this mailer wants a Full-Name: line */
		case 'u':	/* preserve upper case in user names */
		case 'h':	/* preserve upper case in host names */
		case 'A':	/* arpanet-compatibility */
		case 'e':	/* expensive mailer */
		case 'L':	/* limit line length */
		case 'I':	/* talking to a clone of I */
		case 'C':	/* canonicalize remote hostnames */
			(void) fprintf(stderr,
"%s: the '%c' sendmail mailer option does not make sense in this environment\n",
				progname, *cp);
			break;
		case '-':	/* ignore */
			break;
		default:
			(void) fprintf(stderr,
				"%s: unknown sendmail mailer option '%c'\n",
				progname, *cp);
			break;
		}
	}
	SKIPWHILE(isspace, cp);
	m.command = cp;
	SKIPWHILE(!isspace, cp);
	if (cp == m.command) {
		(void)fprintf(stderr,"%s: bad entry for %s\n",progname, m.name);
		return NULL;
	}
	*cp++ = '\0';
	if (*m.command != '/') {
		char *nmc, *mailbin = getzenv("MAILBIN");

		if (mailbin == NULL)
			mailbin = MAILBIN;
		
		nmc = emalloc(strlen(mailbin)+1+strlen(m.command)+1);
		sprintf(nmc, "%s/%s", mailbin, m.command);
		m.command = nmc;
	}
	SKIPWHILE(isspace, cp);
	i = 0;
	while (isascii(*cp) && !isspace(*cp)) {
		if (*cp == '\0')
			break;
		m.argv[i++] = cp;
		SKIPWHILE(!isspace, cp);
		if (isascii(*cp)) {
			*cp++ = '\0';
			SKIPWHILE(isspace, cp);
		}
	}
	if (i == 0) {
		(void) fprintf(stderr,
			       "%s: bad command for %s\n", progname, m.name);
		return NULL;
	}
	m.argv[i] = NULL;
	return &m;
}

