#ifndef lint
static char sccsid[] = "@(#)spost.c	1.6 (Berkeley) 11/2/85";
#endif

/* spost.c - feed messages to sendmail */
/*
 * (This is a simpler, faster, replacement for "post" for use when "sendmail"
 * is the transport system)
 */

#include <ctype.h>
#include <stdio.h>
#include <sys/file.h>
#include "../h/mh.h"
#include "../h/addrsbr.h"
#include "../h/aliasbr.h"
#include "../h/dropsbr.h"
#include "../zotnet/tws.h"

extern char *getfullname(), *getusr();
extern int  errno;
extern int  sys_nerr;
extern char *sys_errlist[];

#define	uptolow(c)	(isupper (c) ? tolower (c) : c)

#define	SENDMAIL	"/usr/sbin/sendmail"
#define MAX_SM_FIELD	1476	/* < largest hdr field sendmail will accept */
#define FCCS		10	/* max number of fccs allowed */

struct swit switches[] = {
#define	FILTSW	0
	"filter filterfile", 0,
#define	NFILTSW	1
	"nofilter", 0,

#define	FRMTSW	2
	"format", 0,
#define	NFRMTSW	3
	"noformat", 0,

#define	REMVSW	4
	"remove", 0,
#define	NREMVSW	5
	"noremove", 0,

#define	VERBSW	6
	"verbose", 0,
#define	NVERBSW	7
	"noverbose", 0,

#define	WATCSW	8
	"watch", 0,
#define	NWATCSW	9
	"nowatch", 0,

#define	HELPSW	10
	"help", 4,

#define	DEBUGSW	11
	"debug", -5,

#define	DISTSW	12
	"dist", -4,		/* interface from dist */

#define BACKSW 13
	"backup", 0,
#define NBACKSW 14
	"nobackup", 0,

#define CHKSW 15
	"check", -5,		/* interface from whom */
#define NCHKSW 16
	"nocheck", -7,		/* interface from whom */
#define WHOMSW 17
	"whom", -4,		/* interface from whom */

#define PUSHSW 18		/* fork to sendmail then exit */
	"push", -4,
#define NPUSHSW 19		/* exec sendmail */
	"nopush", -6,

#define ALIASW 20
	"alias aliasfile", 0,
#define NALIASW 21
	"noalias", 0,

#define WIDTHSW 22
	"width columns", 0,

#define LIBSW 23
	"library directory", -7,

#define	ANNOSW	24
	"idanno number", -6,

	NULL, NULL
};

struct headers {
	char *value;
	unsigned int flags;
#define	HNOP	0x0000		/* just used to keep .set around */
#define	HBAD	0x0001		/* bad header - don't let it through */
#define	HADR	0x0002		/* header has an address field */
#define	HSUB	0x0004		/* Subject: header */
#define	HTRY	0x0008		/* try to send to addrs on header */
#define	HBCC	0x0010		/* don't output this header */
#define	HMNG	0x0020		/* mung this header */
#define	HNGR	0x0040		/* no groups allowed in this header */
#define	HFCC	0x0080		/* FCC: type header */
#define	HNIL	0x0100		/* okay for this header not to have addrs */
#define	HIGN	0x0200		/* ignore this header */
	unsigned int set;
#define	MFRM	0x0001		/* we've seen a From: */
#define	MDAT	0x0002		/* we've seen a Date: */
#define	MRFM	0x0004		/* we've seen a Resent-From: */
#define	MVIS	0x0008		/* we've seen sighted addrs */
#define	MINV	0x0010		/* we've seen blind addrs */
#define	MRDT	0x0020		/* we've seen a Resent-Date: */
};

static struct headers NHeaders[] = {
	"Return-Path", HBAD, NULL,
	"Received", HBAD, NULL,
	"Reply-To", HADR | HNGR, NULL,
	"From", HADR | HNGR, MFRM,
	"Sender", HADR | HBAD, NULL,
	"Date", HNOP, MDAT,
	"Subject", HSUB, NULL,
	"To", HADR | HTRY, MVIS,
	"cc", HADR | HTRY, MVIS,
	"Bcc", HADR | HTRY | HBCC | HNIL, MINV,
	"Message-Id", HBAD, NULL,
	"Fcc", HFCC, NULL,
	NULL
};

static struct headers RHeaders[] = {
	"Resent-Reply-To", HADR | HNGR, NULL,
	"Resent-From", HADR | HNGR, MRFM,
	"Resent-Sender", HADR | HBAD, NULL,
	"Resent-Date", HNOP, MRDT,
	"Resent-Subject", HSUB, NULL,
	"Resent-To", HADR | HTRY, MVIS,
	"Resent-cc", HADR | HTRY, MVIS,
	"Resent-Bcc", HADR | HTRY | HBCC, MINV,
	"Resent-Message-Id", HBAD, NULL,
	"Resent-Fcc", HFCC, NULL,
	"Reply-To", HADR, NULL,
	"Fcc", HIGN, NULL,
	NULL
};

static short fccind = 0;	/* index into fccfold[] */
static int badmsg = 0;		/* message has bad semantics */
static int verbose = 0;		/* spell it out */
static int debug = 0;		/* debugging post */
static int rmflg = 1;		/* remove temporary file when done */
static int watch = 0;		/* watch the delivery process */
static int backflg = 0;		/* rename input file as *.bak when done */
static int whomflg = 0;		/* if just checking addresses */
static int pushflg = 0;		/* if going to fork to sendmail */
static int aliasflg = -1;	/* if going to process aliases */
static int outputlinelen = 72;
static unsigned msgflags = 0;	/* what we've seen */

static enum {
	normal, resent
} msgstate = normal;

static char tmpfil[] = "/tmp/pstXXXXXX";
static char from[BUFSIZ];	/* my network address */
static char signature[BUFSIZ];	/* my signature */
static char *filter = NULL;	/* the filter for BCC'ing */
static char *subject = NULL;	/* the subject field for BCC'ing */
static char *fccfold[FCCS];	/* foldernames for FCC'ing */
static struct headers *hdrtab;	/* table for the message we're doing */
static FILE *out;		/* output (temp) file */

static int get_header(), putone();
static void putfmt(), start_headers(), finish_headers(), putadr(),
	    insert_fcc(), file(), fcc(), die();

/* ARGSUSED */
main(argc, argv)
	int argc;
	char *argv[];
{
	int state, i, pid, compnum;
	char *cp, *msg = NULL, **argp = argv + 1,
	     *sargv[16], buf[BUFSIZ], name[NAMESZ], *arguments[MAXARGS];
	FILE *in;

	invo_name = r1bindex(argv[0], '/');
	mts_init(invo_name);
	if ((cp = m_find(invo_name)) != NULL) {
		argp = copyip(brkstring(cp, " ", "\n"), arguments);
		(void) copyip(argv + 1, argp);
		argp = arguments;
	}
	while (cp = *argp++) {
		if (*cp == '-')
			switch (smatch(++cp, switches)) {
			case AMBIGSW:
				ambigsw(cp, switches);
				done(1);
			case UNKWNSW:
				adios(NULLCP, "-%s unknown", cp);
			case HELPSW:
				(void) sprintf(buf, "%s [switches] file",
					       invo_name);
				help(buf, switches);
				done(1);

			case DEBUGSW:
				debug++;
				continue;

			case DISTSW:
				msgstate = resent;
				continue;

			case WHOMSW:
				whomflg++;
				continue;

			case FILTSW:
				if (!(filter = *argp++) || *filter == '-')
					adios(NULLCP, "missing argument to %s",
					      argp[-2]);
				continue;
			case NFILTSW:
				filter = NULL;
				continue;

			case REMVSW:
				rmflg++;
				continue;
			case NREMVSW:
				rmflg = 0;
				continue;

			case BACKSW:
				backflg++;
				continue;
			case NBACKSW:
				backflg = 0;
				continue;

			case VERBSW:
				verbose++;
				continue;
			case NVERBSW:
				verbose = 0;
				continue;

			case WATCSW:
				watch++;
				continue;
			case NWATCSW:
				watch = 0;
				continue;

			case PUSHSW:
				pushflg++;
				continue;
			case NPUSHSW:
				pushflg = 0;
				continue;

			case ALIASW:
				if (!(cp = *argp++) || *cp == '-')
					adios(NULLCP, "missing argument to %s",
					      argp[-2]);
				if (aliasflg < 0)
					/* load default aka's */
					(void) alias(AliasFile);
				aliasflg = 1;
				if ((state = alias(cp)) != AK_OK)
					adios(NULLCP,
					      "aliasing error in file %s - %s",
					      cp, akerror(state));
				continue;
			case NALIASW:
				aliasflg = 0;
				continue;

			case WIDTHSW:
				if (!(cp = *argp++) || *cp == '-')
					adios(NULLCP, "missing argument to %s",
					      argp[-2]);
				outputlinelen = atoi(cp);
				if (outputlinelen <= 10)
					outputlinelen = 72;
				continue;

			case LIBSW:
			case ANNOSW:
				/* -library & -idanno switch ignored */
				if (!(cp = *argp++) || *cp == '-')
					adios(NULLCP, "missing argument to %s",
					      argp[-2]);
				continue;
			}
		if (msg)
			adios(NULLCP, "only one message at a time!");
		else
			msg = cp;
	}

	if (aliasflg < 0)
		alias(AliasFile);	/* load default aka's */

	if (!msg)
		adios(NULLCP, "usage: %s [switches] file", invo_name);

	if ((in = fopen(msg, "r")) == NULL)
		adios(msg, "unable to open");

	start_headers();
	if (debug) {
		verbose++;
		out = stdout;
	} else {
		(void) mktemp(tmpfil);
		if ((out = fopen(tmpfil, "w")) == NULL)
			adios(tmpfil, "unable to create");
		(void) chmod(tmpfil, 0600);
	}

	hdrtab = (msgstate == normal) ? NHeaders : RHeaders;

	for (compnum = 1, state = FLD;;) {
		switch (state = m_getfld(state, name, buf, sizeof buf, in)) {
		case FLD:
			compnum++;
			putfmt(name, buf, out);
			continue;

		case FLDPLUS:
			compnum++;
			cp = add(buf, cp);
			while (state == FLDPLUS) {
				state = m_getfld(state, name, buf, sizeof buf, in);
				cp = add(buf, cp);
			}
			putfmt(name, cp, out);
			free(cp);
			continue;

		case BODY:
			finish_headers(out);
			fprintf(out, "\n%s", buf);
			if (whomflg == 0)
				while (state == BODY) {
					state = m_getfld(state, name, buf,
							 sizeof buf, in);
					fputs(buf, out);
				}
			break;

		case FILEEOF:
			finish_headers(out);
			break;

		case LENERR:
		case FMTERR:
			adios(NULLCP, "message format error in component #%d",
			      compnum);

		default:
			adios(NULLCP, "getfld() returned %d", state);
		}
		break;
	}

	(void) fclose(in);
	if (backflg && !whomflg) {
		(void) strcpy(buf, m_backup(msg));
		if (rename(msg, buf) == NOTOK)
			advise(buf, "unable to rename %s to", msg);
	}
	if (debug) {
		done(0);
	} else
		(void) fclose(out);

	file(tmpfil);

	/*
	 * re-open the temp file, unlink it and exec sendmail, giving it the
	 * msg temp file as std in.
	 */
	if (freopen(tmpfil, "r", stdin) == NULL)
		adios(tmpfil, "can't reopen for sendmail");
	if (rmflg)
		(void) unlink(tmpfil);

	argp = sargv;
	*argp++ = "send-mail";
	*argp++ = "-m";		/* send to me too */
	*argp++ = "-t";		/* read msg for recipients */
	*argp++ = "-i";		/* don't stop on "." */
	if (whomflg)
		*argp++ = "-bv";
	if (watch || verbose)
		*argp++ = "-v";
	*argp = NULL;

	if (pushflg && !(watch || verbose)) {
		/* Insure sendmail exists if using the push flag */
		errno = 0;
		if (access(SENDMAIL, X_OK) < 0) {
			adios(SENDMAIL, errno > 0 && errno < sys_nerr ?
			    sys_errlist[errno] : "unknown error");
		}

		/* fork to a child to run sendmail */
		for (i = 0; (pid = vfork()) == NOTOK && i < 5; i++)
			sleep(5);
		switch (pid) {
		case NOTOK:
			fprintf(verbose ? stdout : stderr,
			        "%s: can't fork to %s\n", invo_name, SENDMAIL);
			exit(-1);
		case OK:
			/* we're the child .. */
			break;
		default:
			exit(0);
		}
	}
	execv(SENDMAIL, sargv);
	adios(SENDMAIL, "can't exec");
}

/* DRAFT GENERATION */
static void
putfmt(name, str, out)
	char *name, *str;
	FILE *out;
{
	int count, grp, i, keep;
	char *cp, *pp, *qp, namep[BUFSIZ];
	struct mailname *mp, *np;
	struct headers *hdr;

	while (*str == ' ' || *str == '\t')
		str++;

	if ((i = get_header(name, hdrtab)) == NOTOK) {
		fprintf(out, "%s: %s", name, str);
		return;
	}
	hdr = &hdrtab[i];
	if (hdr->flags & HIGN)
		return;
	if (hdr->flags & HBAD) {
		advise(NULLCP, "illegal header line -- %s:", name);
		badmsg++;
		return;
	}
	msgflags |= hdr->set;

	if (hdr->flags & HSUB)
		subject = subject ? add(str, add("\t", subject)) : getcpy(str);

	if (hdr->flags & HFCC) {
		if (cp = rindex(str, '\n'))
			*cp = NULL;
		for (cp = pp = str; cp = index(pp, ','); pp = cp) {
			*cp++ = NULL;
			insert_fcc(hdr, pp);
		}
		insert_fcc(hdr, pp);
		return;
	}
	if (*str != '\n' && *str != '\0')
		if (aliasflg && hdr->flags & HTRY) {
			/*
			 * this header contains address(es) that we have to
			 * do alias expansion on.  Because of the saved state
			 * in getname we have to put all the addresses into a
			 * list. We then let putadr munch on that list,
			 * possibly expanding aliases.
			 */
			register struct mailname *f = 0;
			register struct mailname *mp = 0;

			while (cp = getname(str)) {
				mp = getm(cp, NULLCP, 0, AD_HOST, NULLCP);
				if (f == 0) {
					f = mp;
					mp->m_next = mp;
				} else {
					mp->m_next = f->m_next;
					f->m_next = mp;
					f = mp;
				}
			}
			/*
			 * This error only seems to occur when a
			 * message contains an address that is
			 * incorrectly split to a continuation line
			 * (e.g. the continuation line is missing the
			 * required white space).
			 */
			if (f == 0 || mp == 0) {
				advise(NULLCP, "bogus address -- \"%s\"", str);
				badmsg++;
				return;
			}
			f = mp->m_next;
			mp->m_next = 0;
			putadr(name, f, hdr->flags & HBCC);
		} else {
			fprintf(out, "%s: %s", name, str);
		}
}

static void
start_headers()
{
	char *cp;
	char sigbuf[BUFSIZ];

	(void) strcpy(from, getusr());

	if ((cp = getfullname()) && *cp) {
		(void) strcpy(sigbuf, cp);
		(void) sprintf(signature, "%s <%s>", sigbuf, from);
	} else
		(void) sprintf(signature, "%s", from);
}

static char **bcc_list;
static int bcc_list_size;
static int bcc_list_next;

static void
add_bcc(name)
	char *name;
{
	if (bcc_list_next >= bcc_list_size) {
		if (! bcc_list) {
			bcc_list_size = 32;
			bcc_list = (char **)malloc(32 * sizeof(*bcc_list));
		} else {
			bcc_list_size <<= 1;
			bcc_list = (char **)realloc(bcc_list,
					   bcc_list_size * sizeof(*bcc_list));
		}
	}
	bcc_list[bcc_list_next++] = getcpy(name);
}

static void
put_bcc_list(name)
	char *name;
{
	register int i, linepos, namelen;

	if (! bcc_list_next)
		return;

	fprintf(out, "%s: ", name);
	namelen = strlen(name) + 2;
	linepos = namelen;
	for (i = 0; i < bcc_list_next; ++i) {
		if (linepos > MAX_SM_FIELD) {
			fprintf(out, "\n%s: ", name);
			linepos = namelen;
		}
		linepos = putone(bcc_list[i], linepos, namelen);
	}
	putc('\n', out);
}

static void
finish_headers(out)
	FILE *out;
{
	switch (msgstate) {
	case normal:
		put_bcc_list("bcc");
		if (!(msgflags & MDAT))
			fprintf(out, "Date: %s\n", dtimenow());
		if (msgflags & MFRM)
			fprintf(out, "Sender: %s\n", from);
		else
			fprintf(out, "From: %s\n", signature);
		break;

	case resent:
		put_bcc_list("resent-bcc");
		if (!(msgflags & MRDT))
			fprintf(out, "Resent-Date: %s\n", dtimenow());
		if (msgflags & MRFM)
			fprintf(out, "Resent-Sender: %s\n", from);
		else
			fprintf(out, "Resent-From: %s\n", signature);
		break;
	}

	if (badmsg)
		adios(NULLCP, "re-format message and try again");
}

static int
get_header(header, table)
	char *header;
	struct headers *table;
{
	struct headers *h;

	for (h = table; h->value; h++)
		if (uleq(header, h->value))
			return (h - table);

	return NOTOK;
}

/*
 * output the address list for header "name".  The address list is a linked
 * list of mailname structs.  "nl" points to the head of the list.  Alias
 * substitution should be done on nl.
 */
static void
putadr(name, nl, isbcc)
	char *name;
	struct mailname *nl;
	int isbcc;
{
	register struct mailname *mp, *mp2;
	register int linepos;
	register char *cp;
	int namelen;

	fprintf(out, "%s: ", name);
	namelen = strlen(name) + 2;
	linepos = namelen;

	for (mp = nl; mp;) {
		if (linepos > MAX_SM_FIELD) {
			fprintf(out, "\n%s: ", name);
			linepos = namelen;
		}
		if (mp->m_nohost && (cp = akvalue(mp->m_mbox)) != mp->m_mbox) {
			/*
			 * an alias - if 'invisible' or we're doing
			 * bcc's, expand it.  Otherwise just add it's
			 * names to the bcc list.
			 */
			if (!isbcc && akvisible()) {
				char tmpname[NAMESZ];

				sprintf(tmpname, "(%s)", mp->m_mbox);
				linepos = putone(tmpname, linepos, namelen);
			}
			while (cp = getname(cp)) {
				if (linepos > MAX_SM_FIELD) {
					fprintf(out, "\n%s: ", name);
					linepos = namelen;
				}
				mp2 = getm(cp, NULLCP, 0, AD_HOST, NULLCP);
				if (!isbcc && akvisible())
					add_bcc(mp2->m_text);
				else
					linepos = putone(mp2->m_text, linepos,
							 namelen);
				mnfree(mp2);
			}
		} else
			/* not a local name - use what the user typed */
			linepos = putone(mp->m_text, linepos, namelen);
		mp2 = mp;
		mp = mp->m_next;
		mnfree(mp2);
	}
	putc('\n', out);
}

static int
putone(adr, pos, indent)
	register char *adr;
	register int pos;
	int indent;
{
	register int len;
	static int linepos;

	len = strlen(adr);
	if (pos == indent)
		linepos = pos;
	else if (linepos + len > outputlinelen) {
		fprintf(out, ",\n%*s", indent, "");
		linepos = indent;
		pos += indent + 2;
	} else {
		fputs(", ", out);
		linepos += 2;
		pos += 2;
	}
	fputs(adr, out);
	linepos += len;
	return (pos + len);
}

static void
insert_fcc(hdr, pp)
	struct headers *hdr;
	char *pp;
{
	char *cp;

	for (cp = pp; isspace(*cp); cp++)
		continue;
	for (pp += strlen(pp) - 1; pp > cp && isspace(*pp); pp--)
		continue;
	if (pp >= cp)
		*++pp = NULL;
	if (*cp == NULL)
		return;

	if (fccind >= FCCS)
		adios(NULLCP, "too many %ss", hdr->value);
	fccfold[fccind++] = getcpy(cp);
}

static void
file(path)
	char *path;
{
	int i;

	if (fccind == 0)
		return;

	for (i = 0; i < fccind; i++)
		if (whomflg)
			printf("Fcc: %s\n", fccfold[i]);
		else
			fcc(path, fccfold[i]);
}

static void
fcc(file, folder)
	char *file, *folder;
{
	int i, child_id, status;
	char fold[BUFSIZ];

	if (verbose)
		printf("%sFcc: %s\n", msgstate == resent ? "Resent-" : "",
		       folder);
	(void) fflush(stdout);

	for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
		sleep(5);
	switch (child_id) {
	case NOTOK:
		if (!verbose)
			fprintf(stderr, "  %sFcc %s: ",
				msgstate == resent ? "Resent-" : "", folder);
		fprintf(verbose ? stdout : stderr, "no forks, so not ok\n");
		break;

	case OK:
		(void) sprintf(fold, "%s%s",
		       *folder == '+' || *folder == '@' ? "" : "+", folder);
		execlp(fileproc, r1bindex(fileproc, '/'),
		       "-link", "-file", file, fold, NULL);
		_exit(-1);

	default:
		if (status = pidwait(child_id)) {
			if (!verbose)
				fprintf(stderr, "  %sFcc %s: ",
				msgstate == resent ? "Resent-" : "", folder);
			fprintf(verbose ? stdout : stderr,
				" errored (0%o)\n", status);
		}
	}

	(void) fflush(stdout);
}

/* VARARGS2 */
static void
die(what, fmt, a, b, c, d)
	char *what, *fmt, *a, *b, *c, *d;
{
	adios(what, fmt, a, b, c, d);
}
