/*
** slocal.c -- asynchronously filter and deliver new mail
**
** This code is Copyright (c) 2002, by the authors of nmh.  See the
** COPYRIGHT file in the root directory of the nmh distribution for
** complete copyright information.
*/

/*
**  Under sendmail, users should add the line
**
**      "| /usr/local/mmh/bin/slocal"
**
**  to their $HOME/.forward file.
**
*/

#include <h/mh.h>
#include <h/rcvmail.h>
#include <h/signals.h>
#include <h/tws.h>
#include <h/utils.h>
#include <pwd.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <locale.h>
#include <sysexits.h>

#ifdef INITGROUPS_HEADER
#include INITGROUPS_HEADER
#else
/*
** On AIX 4.1, initgroups() is defined and even documented (giving the
** parameter types as char* and int), but doesn't have a prototype in any
** of the system header files.  AIX 4.3, SunOS 4.1.3, and ULTRIX 4.2A have
** the same problem.
*/
extern int  initgroups(char*, int);
#endif

static struct swit switches[] = {
#define ADDRSW  0
	{ "addr address", 0 },
#define USERSW  1
	{ "user name", 0 },
#define FILESW  2
	{ "file file", 0 },
#define SENDERSW  3
	{ "sender address", 0 },
#define MAILBOXSW  4
	{ "mailbox file", 0 },
#define HOMESW  5
	{ "home directory", -4 },
#define INFOSW  6
	{ "info data", 0 },
#define MAILSW  7
	{ "maildelivery file", 0 },
#define VERBSW  8
	{ "verbose", 0 },
#define NVERBSW  9
	{ "noverbose", 2 },
#define DEBUGSW  10
	{ "debug", 0 },
#define VERSIONSW  11
	{ "Version", 0 },
#define HELPSW  12
	{ "help", 0 },
	{ NULL, 0 }
};

char *version=VERSION;

static int globbed = 0;  /* have we built "vars" table yet? */
static int parsed = 0;  /* have we built header field table yet */

static int verbose = 0;
static int debug = 0;

static char *addr = NULL;
static char *user = NULL;
static char *info = NULL;
static char *file = NULL;
static char *sender = NULL;
static char *envelope = NULL;  /* envelope information ("From " line)  */
static char *mbox = NULL;
static char *home = NULL;

static struct passwd *pw;  /* passwd file entry */

static char ddate[BUFSIZ];  /* record the delivery date */
struct tws *now;

volatile sig_atomic_t eflag = 0; /* flag to indecate interrupt */
static volatile pid_t child_id;

/* flags for pair->p_flags */
#define P_NIL  0x00
#define P_ADR  0x01  /* field is address */
#define P_HID  0x02  /* special (fake) field */
#define P_CHK  0x04

struct pair {
	char *p_name;
	char *p_value;
	char  p_flags;
};

#define NVEC 100

/*
** Lookup table for matching fields and patterns
** in messages.  The rest of the table is added
** when the message is parsed.
*/
static struct pair hdrs[NVEC + 1] = {
	{ "source",  NULL, P_HID },
	{ "addr",  NULL, P_HID },
	{ "Return-Path",  NULL, P_ADR },
	{ "Reply-To",  NULL, P_ADR },
	{ "From",  NULL, P_ADR },
	{ "Sender",  NULL, P_ADR },
	{ "To",  NULL, P_ADR },
	{ "cc",  NULL, P_ADR },
	{ "Resent-Reply-To", NULL, P_ADR },
	{ "Resent-From",  NULL, P_ADR },
	{ "Resent-Sender",   NULL, P_ADR },
	{ "Resent-To",  NULL, P_ADR },
	{ "Resent-Cc",  NULL, P_ADR },
	{ NULL, NULL, 0 }
};

/*
** The list of builtin variables to expand in a string
** before it is executed by the "pipe" or "qpipe" action.
*/
static struct pair vars[] = {
	{ "sender",   NULL, P_NIL },
	{ "address",  NULL, P_NIL },
	{ "size",  NULL, P_NIL },
	{ "reply-to", NULL, P_CHK },
	{ "info",  NULL, P_NIL },
	{ NULL, NULL, 0 }
};

extern char **environ;

/*
** static prototypes
*/
static int localmail(int, char *);
static int usr_delivery(int, char *, int);
static int split(char *, char **);
static int parse(int);
static void expand(char *, char *, int);
static void glob(int);
static struct pair *lookup(struct pair *, char *);
static int usr_file(int, char *);
static int usr_pipe(int, char *, char *, char **, int);
static int usr_folder(int, char *);
static void alrmser(int);
static void get_sender(char *, char **);
static int copy_message(int, char *, int);
static void verbose_printf(char *fmt, ...);
static void adorn(char *, char *, ...);
static void debug_printf(char *fmt, ...);
static char *trimstr(char *);


int
main(int argc, char **argv)
{
	int fd, status;
	FILE *fp = stdin;
	char *cp, *mdlvr = NULL, buf[BUFSIZ];
	char mailbox[BUFSIZ], tmpfil[BUFSIZ];
	char **argp, **arguments;

	setlocale(LC_ALL, "");
	invo_name = mhbasename(*argv);

	arguments = getarguments(invo_name, argc, argv, 0);
	argp = arguments;

	/* Parse arguments */
	while ((cp = *argp++)) {
		if (*cp == '-') {
			switch (smatch(++cp, switches)) {
			case AMBIGSW:
				ambigsw(cp, switches);
				exit(EX_USAGE);
			case UNKWNSW:
				adios(EX_USAGE, NULL, "-%s unknown", cp);

			case HELPSW:
				snprintf(buf, sizeof(buf), "%s [switches] [address info sender]", invo_name);
				print_help(buf, switches, 0);
				exit(argc == 2 ? EX_OK : EX_USAGE);
			case VERSIONSW:
				print_version(invo_name);
				exit(argc == 2 ? EX_OK : EX_USAGE);

			case ADDRSW:
				if (!(addr = *argp++)) {
					/* allow -xyz arguments */
					adios(EX_USAGE, NULL, "missing argument to %s",
							argp[-2]);
				}
				continue;
			case INFOSW:
				if (!(info = *argp++)) {
					/* allow -xyz arguments */
					adios(EX_USAGE, NULL, "missing argument to %s",
							argp[-2]);
				}
				continue;
			case USERSW:
				if (!(user = *argp++)) {
					/* allow -xyz arguments */
					adios(EX_USAGE, NULL, "missing argument to %s",
							argp[-2]);
				}
				continue;
			case FILESW:
				if (!(file = *argp++) || *file == '-') {
					adios(EX_USAGE, NULL, "missing argument to %s",
							argp[-2]);
				}
				continue;
			case SENDERSW:
				if (!(sender = *argp++)) {
					/* allow -xyz arguments */
					adios(EX_USAGE, NULL, "missing argument to %s",
							argp[-2]);
				}
				continue;
			case MAILBOXSW:
				if (!(mbox = *argp++) || *mbox == '-') {
					adios(EX_USAGE, NULL, "missing argument to %s",
							argp[-2]);
				}
				continue;
			case HOMESW:
				if (!(home = *argp++) || *home == '-') {
					adios(EX_USAGE, NULL, "missing argument to %s",
							argp[-2]);
				}
				continue;

			case MAILSW:
				if (!(cp = *argp++) || *cp == '-') {
					adios(EX_USAGE, NULL, "missing argument to %s",
							argp[-2]);
				}
				if (mdlvr) {
					adios(EX_USAGE, NULL, "only one maildelivery file at a time!");
				}
				mdlvr = cp;
				continue;

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

			case DEBUGSW:
				debug++;
				continue;
			}
		}

		switch (argp - (argv + 1)) {
		case 1:
			addr = cp;
			break;
		case 2:
			info = cp;
			break;
		case 3:
			sender = cp;
			break;
		}
	}

	if (!addr) {
		addr = getusername();
	}
	if (!user) {
		user = (cp = strchr(addr, '.')) ? ++cp : addr;
	}
	if (!(pw = getpwnam(user))) {
		adios(EX_NOUSER, NULL, "no such local user as %s", user);
	}

	if (chdir(pw->pw_dir) == -1) {
		chdir("/");
	}
	umask(0077);

	if (geteuid() == 0) {
		setgid(pw->pw_gid);
		initgroups(pw->pw_name, pw->pw_gid);
		setuid(pw->pw_uid);
	}

	if (!info) {
		info = "";
	}

	setbuf(stdin, NULL);

	/* Record the delivery time */
	if (!(now = dlocaltimenow())) {
		adios(EX_OSERR, NULL, "unable to ascertain local time");
	}
	snprintf(ddate, sizeof(ddate), "Delivery-Date: %s\n", dtimenow());

	/*
	** Copy the message to a temporary file
	*/
	if (file) {
		int tempfd;

		/* getting message from file */
		if ((tempfd = open(file, O_RDONLY)) == -1) {
			adios(EX_IOERR, file, "unable to open");
		}
		if (debug) {
			debug_printf("retrieving message from file \"%s\"\n",
					file);
		}
		if ((fd = copy_message(tempfd, tmpfil, 1)) == -1) {
			adios(EX_CANTCREAT, NULL, "unable to create temporary file");
		}
		close(tempfd);
	} else {
		/* getting message from stdin */
		if (debug) {
			debug_printf("retrieving message from stdin\n");
		}
		if ((fd = copy_message(fileno(stdin), tmpfil, 1)) == -1) {
			adios(EX_CANTCREAT, NULL, "unable to create temporary file");
		}
	}

	if (debug) {
		debug_printf("temporary file=\"%s\"\n", tmpfil);
	}

	/*
	** Delete the temp file now or a copy of every single message
	** passed through slocal will be left in the /tmp directory until
	** deleted manually!  This unlink() used to be under an 'else'
	** of the 'if (debug)' above, but since some people like to
	** always run slocal with -debug and log the results, the /tmp
	** directory would get choked over time.  Of course, now that
	** we always delete the temp file, the "temporary file=" message
	** above is somewhat pointless -- someone watching debug output
	** wouldn't have a chance to 'tail -f' or 'ln' the temp file
	** before it's unlinked.  The best thing would be to delay this
	** unlink() until later if debug == 1, but I'll leave that for
	** someone who cares about the temp-file-accessing functionality
	** (they'll have to watch out for cases where we adios()).
	*/
	unlink(tmpfil);

	if (!(fp = fdopen(fd, "r+"))) {
		adios(EX_IOERR, NULL, "unable to access temporary file");
	}

	/* If no sender given, extract it from envelope information. */
	if (!sender) {
		get_sender(envelope, &sender);
	}
	if (!mbox) {
		snprintf(mailbox, sizeof(mailbox), "%s/%s",
				mailspool, pw->pw_name);
		mbox = mailbox;
	}
	if (!home) {
		home = pw->pw_dir;
	}

	if (debug) {
		debug_printf("addr=\"%s\"\n", trimstr(addr));
		debug_printf("user=\"%s\"\n", trimstr(user));
		debug_printf("info=\"%s\"\n", trimstr(info));
		debug_printf("sender=\"%s\"\n", trimstr(sender));
		debug_printf("envelope=\"%s\"\n",
				envelope ? trimstr(envelope) : "");
		debug_printf("mbox=\"%s\"\n", trimstr(mbox));
		debug_printf("home=\"%s\"\n", trimstr(home));
		debug_printf("ddate=\"%s\"\n", trimstr(ddate));
		debug_printf("now=%02d:%02d\n\n", now->tw_hour, now->tw_min);
	}

	/* deliver the message */
	status = localmail(fd, mdlvr);

	return (status != -1 ? RCV_MOK : RCV_MBX);
}


/*
** Main routine for delivering message.
*/
static int
localmail(int fd, char *mdlvr)
{
	char buf[BUFSIZ];

	/* delivery according to personal Maildelivery file */
	if (usr_delivery(fd, mdlvr ? mdlvr : ".maildelivery", 0) != -1) {
		return 0;
	}
	/* delivery according to global Maildelivery file */
	snprintf(buf, sizeof buf, "%s/%s", mhetcdir, "maildelivery");
	if (usr_delivery(fd, buf, 1) != -1) {
		return 0;
	}
	if (verbose) {
		verbose_printf("(delivering to standard mail spool)\n");
	}
	/* last resort - deliver to standard mail spool */
	return usr_file(fd, mbox);
}


#define matches(a,b) (stringdex(b, a) >= 0)

/*
** Parse the delivery file, and process incoming message.
*/
static int
usr_delivery(int fd, char *delivery, int su)
{
	int i, accept, status=1, won, vecp, next;
	char *field, *pattern, *action, *result, *string;
	char buffer[BUFSIZ], tmpbuf[BUFSIZ];
	char *cp, *vec[NVEC];
	struct stat st;
	struct pair *p;
	FILE *fp;

	/* open the delivery file */
	if (!(fp = fopen(delivery, "r"))) {
		return -1;
	}
	/* check if delivery file has bad ownership or permissions */
	if (fstat(fileno(fp), &st) == -1 ||
			(st.st_uid != 0 && (su || st.st_uid != pw->pw_uid)) ||
			st.st_mode & (S_IWGRP|S_IWOTH)) {
		if (verbose) {
			verbose_printf("WARNING: %s has bad ownership/modes (su=%d,uid=%d,owner=%d,mode=0%o)\n", delivery, su, (int) pw->pw_uid, (int) st.st_uid, (int) st.st_mode);
		}
		return -1;
	}

	won = 0;
	next = 1;

	/* read and process delivery file */
	while (fgets(buffer, sizeof(buffer), fp)) {
		/* skip comments and empty lines */
		if (*buffer == '#' || *buffer == '\n') {
			continue;
		}
		/* zap trailing newline */
		if ((cp = strchr(buffer, '\n'))) {
			*cp = '\0';
		}
		/* split buffer into fields */
		vecp = split(buffer, vec);

		/* check for too few fields */
		if (vecp < 5) {
			if (debug)
				debug_printf("WARNING: entry with only %d fields, skipping.\n", vecp);
			continue;
		}

		if (debug) {
			for (i = 0; vec[i]; i++) {
				debug_printf("vec[%d]: \"%s\"\n",
						i, trimstr(vec[i]));
			}
		}

		field   = vec[0];
		pattern = vec[1];
		action  = vec[2];
		result  = vec[3];
		string  = vec[4];

		/* find out how to perform the action */
		switch (result[0]) {
		case 'N':
		case 'n':
			/*
			** If previous condition failed, don't
			** do this - else fall through
			*/
			if (!next) {
				continue;
			}
			/* fall */

		case '?':
			/*
			** If already delivered, skip this action.
			** Else consider delivered if action is
			** successful.
			*/
			if (won) {
				continue;
			}
			/* fall */

		case 'A':
		case 'a':
			/*
			** Take action, and consider delivered if
			** action is successful.
			*/
			accept = 1;
			break;

		case 'R':
		case 'r':
		default:
			/*
			** Take action, but don't consider delivered,
			** even if action is successful
			*/
			accept = 0;
			break;
		}

		/* check if the field matches */
		switch (*field) {
		case '*':
			/* always matches */
			break;

		case 'd':
			/*
			** "default" matches only if the message hasn't
			** been delivered yet.
			*/
			if (mh_strcasecmp(field, "default")==0) {
				if (won) {
					continue;
				}
				break;
			}
			/* fall */
		default:
			/* parse message and build lookup table */
			if (!parsed && parse(fd) == -1) {
				fclose(fp);
				return -1;
			}
			/*
			** find header field in lookup table, and
			** see if the pattern matches.
			*/
			if ((p = lookup(hdrs, field)) && p->p_value &&
					matches(p->p_value, pattern)) {
				next = 1;
			} else {
				next = 0;
				continue;
			}
			break;
		}

		/* find out the action to perform */
		switch (*action) {
		case 'q':
			/* deliver to quoted pipe */
			if (mh_strcasecmp(action, "qpipe")) {
				continue;
			}
			/* fall */
		case '^':
			expand(tmpbuf, string, fd);
			if (split(tmpbuf, vec) < 1) {
				continue;
			}
			status = usr_pipe(fd, tmpbuf, vec[0], vec, 0);
			break;

		case 'p':
			/* deliver to pipe */
			if (mh_strcasecmp(action, "pipe")) {
				continue;
			}
			/* fall */
		case '|':
			vec[2] = "sh";
			vec[3] = "-c";
			expand(tmpbuf, string, fd);
			vec[4] = tmpbuf;
			vec[5] = NULL;
			status = usr_pipe(fd, tmpbuf, "/bin/sh", vec+2, 0);
			break;

		case 'f':
			if (mh_strcasecmp(action, "file")==0) {
				/* mbox format */
				status = usr_file(fd, string);
				break;
			}
			if (mh_strcasecmp(action, "folder")!=0) {
				continue;
			}
			/* fall */
		case '+':
			/* deliver to nmh folder */
			status = usr_folder(fd, string);
			break;

		case 'm':
			/* mbox format */
			if (mh_strcasecmp(action, "mbox")!=0) {
				continue;
			}
			/* fall */
		case '>':
			/* mbox format */
			status = usr_file(fd, string);
			break;

		case 'd':
			/* ignore message */
			if (mh_strcasecmp(action, "destroy")!=0) {
				continue;
			}
			status = 0;
			break;
		}

		if (status) {
			next = 0;  /* action failed, mark for 'N' result */
		} else if (accept) {
			won++;
		}
	}

	fclose(fp);
	return (won ? 0 : -1);
}


/*
** Split buffer into fields (delimited by whitespace or
** comma's).  Return the number of fields found.
*/
static int
split(char *cp, char **vec)
{
	int i;
	unsigned char *s;

	s = cp;

	/* split into a maximum of NVEC fields */
	for (i = 0; i <= NVEC;) {
		vec[i] = NULL;

		/* zap any whitespace and comma's */
		while (isspace(*s) || *s == ',') {
			*s++ = '\0';
		}
		/* end of buffer, time to leave */
		if (!*s) {
			break;
		}
		/* get double quote text as a single field */
		if (*s == '"') {
			for (vec[i++] = ++s; *s && *s != '"'; s++) {
				/*
				** Check for escaped double quote.  We need
				** to shift the string to remove slash.
				*/
				if (*s == '\\') {
					if (*++s == '"') {
						strcpy(s - 1, s);
					}
					s--;
				}
			}
			if (*s == '"') {
				/* zap trailing double quote */
				*s++ = '\0';
			}
			continue;
		}

		if (*s == '\\' && *++s != '"') {
			s--;
		}
		vec[i++] = s++;

		/* move forward to next field delimiter */
		while (*s && !isspace(*s) && *s != ',') {
			s++;
		}
	}
	vec[i] = NULL;

	return i;
}


/*
** Parse the headers of a message, and build the
** lookup table for matching fields and patterns.
*/
static int
parse(int fd)
{
	enum state state;
	struct field f = {{0}};
	int i, fd1;
	char *cp, *dp, *lp;
	struct pair *p, *q;
	FILE  *in;

	if (parsed++) {
		return 0;
	}

	/* get a new FILE pointer to message */
	if ((fd1 = dup(fd)) == -1) {
		return -1;
	}
	if (!(in = fdopen(fd1, "r"))) {
		close(fd1);
		return -1;
	}
	rewind(in);

	/* add special entries to lookup table */
	if ((p = lookup(hdrs, "source"))) {
		p->p_value = mh_xstrdup(sender);
	}
	if ((p = lookup(hdrs, "addr"))) {
		p->p_value = mh_xstrdup(addr);
	}

	/*
	** Scan the headers of the message and build a lookup table.
	*/
	for (i = 0, state = FLD2;;) {
		switch (state = m_getfld2(state, &f, in)) {
		case LENERR2:
			state = FLD2;
			/* FALL */

		case FLD2:
			lp = mh_xstrdup(f.value);
			for (p = hdrs; p->p_name; p++) {
				if (mh_strcasecmp(p->p_name, f.name) == 0) {
					if (!(p->p_flags & P_HID)) {
						if ((cp = p->p_value)) {
							if (p->p_flags & P_ADR) {
								dp = cp + strlen(cp) - 1;
								if (*dp == '\n') {
									*dp = '\0';
								}
								cp = add(",\n\t", cp);
							} else {
								cp = add("\t", cp);
							}
						}
						p->p_value = add(lp, cp);
					}
					mh_free0(&lp);
					break;
				}
			}
			if (!p->p_name && i < NVEC) {
				p->p_name = mh_xstrdup(f.name);
				p->p_value = lp;
				p->p_flags = P_NIL;
				p++, i++;
				p->p_name = NULL;
			}
			continue;

		case BODY2:
		case FILEEOF2:
			break;

		case FMTERR2:
		case IOERR2:
			advise(NULL, "format error in message");
			break;

		default:
			advise(NULL, "internal error in m_getfld");
			fclose(in);
			return -1;
		}
		break;
	}
	fclose(in);

	if ((p = lookup(vars, "reply-to"))) {
		if (!(q = lookup(hdrs, "reply-to")) || !q->p_value) {
			q = lookup(hdrs, "from");
		}
		p->p_value = mh_xstrdup(q ? q->p_value : "");
		p->p_flags &= ~P_CHK;
		if (debug) {
			debug_printf("vars[%d]: name=\"%s\" value=\"%s\"\n",
					p - vars, p->p_name, trimstr(p->p_value));
		}
	}
	if (debug) {
		for (p = hdrs; p->p_name; p++) {
			debug_printf("hdrs[%d]: name=\"%s\" value=\"%s\"\n",
					p - hdrs, p->p_name,
					p->p_value ? trimstr(p->p_value) : "");
		}
	}
	return 0;
}


/*
** Expand any builtin variables such as $(sender),
** $(address), etc., in a string.
*/
static void
expand(char *s1, char *s2, int fd)
{
	char c, *cp;
	struct pair *p;

	if (!globbed) {
		glob(fd);
	}
	while ((c = *s2++)) {
		if (c != '$' || *s2 != '(') {
			*s1++ = c;
		} else {
			for (cp = ++s2; *s2 && *s2 != ')'; s2++) {
				continue;
			}
			if (*s2 != ')') {
				s2 = --cp;
				continue;
			}
			*s2++ = '\0';
			if ((p = lookup(vars, cp))) {
				if (!parsed && (p->p_flags & P_CHK)) {
					parse(fd);
				}
				strcpy(s1, p->p_value);
				s1 += strlen(s1);
			}
		}
	}
	*s1 = '\0';
}


/*
** Fill in the information missing from the "vars"
** table, which is necessary to expand any builtin
** variables in the string for a "pipe" or "qpipe"
** action.
*/
static void
glob(int fd)
{
	char buffer[BUFSIZ];
	struct stat st;
	struct pair *p;

	if (globbed++) {
		return;
	}
	if ((p = lookup(vars, "sender"))) {
		p->p_value = mh_xstrdup(sender);
	}
	if ((p = lookup(vars, "address"))) {
		p->p_value = mh_xstrdup(addr);
	}
	if ((p = lookup(vars, "size"))) {
		snprintf(buffer, sizeof(buffer), "%d",
				fstat(fd, &st) != -1 ? (int) st.st_size : 0);
		p->p_value = mh_xstrdup(buffer);
	}
	if ((p = lookup(vars, "info"))) {
		p->p_value = mh_xstrdup(info);
	}
	if (debug) {
		for (p = vars; p->p_name; p++) {
			debug_printf("vars[%d]: name=\"%s\" value=\"%s\"\n",
					p - vars, p->p_name, trimstr(p->p_value));
		}
	}
}


/*
** Find a matching name in a lookup table.  If found,
** return the "pairs" entry, else return NULL.
*/
static struct pair *
lookup(struct pair *pairs, char *key)
{
	for (; pairs->p_name; pairs++) {
		if (!mh_strcasecmp(pairs->p_name, key)) {
			return pairs;
		}
	}
	return NULL;
}


/*
** Deliver message by appending to a file, using rcvpack(1).
*/
static int
usr_file(int fd, char *mailbox)
{
	char *vec[3];

	if (verbose) {
		verbose_printf("delivering to file \"%s\" (mbox style)",
				mailbox);
	}
	vec[0] = "rcvpack";
	vec[1] = mailbox;
	vec[2] = NULL;

	return usr_pipe(fd, "rcvpack", "rcvpack", vec, 1);
}


/*
** Deliver message to a nmh folder, using rcvstore(1).
*/
static int
usr_folder(int fd, char *string)
{
	char folder[BUFSIZ], *vec[3];

	/* get folder name ready */
	if (*string == '+') {
		strncpy(folder, string, sizeof(folder));
	}else {
		snprintf(folder, sizeof(folder), "+%s", string);
	}
	if (verbose) {
		verbose_printf("delivering to folder \"%s\"", folder + 1);
	}
	vec[0] = "rcvstore";
	vec[1] = folder;
	vec[2] = NULL;

	return usr_pipe(fd, "rcvstore", "rcvstore", vec, 1);
}

/*
** Deliver message to a process.
*/
static int
usr_pipe(int fd, char *cmd, char *pgm, char **vec, int suppress)
{
	int bytes, seconds, status, n;
	struct stat st;
	char *path;

	if (verbose && !suppress) {
		verbose_printf("delivering to pipe \"%s\"", cmd);
	}
	lseek(fd, (off_t) 0, SEEK_SET);

	switch ((child_id = fork())) {
	case -1:
		/* fork error */
		if (verbose) {
			adorn("fork", "unable to");
		}
		return -1;

	case 0:
		/* child process */
		if (fd != 0) {
			dup2(fd, 0);
		}
		freopen("/dev/null", "w", stdout);
		freopen("/dev/null", "w", stderr);
		if (fd != 3) {
			dup2(fd, 3);
		}
		for (n=4; n<OPEN_MAX; n++) {
			close(n);
		}

#ifdef TIOCNOTTY
		if ((fd = open("/dev/tty", O_RDWR)) != -1) {
			ioctl(fd, TIOCNOTTY, NULL);
			close(fd);
		}
#endif /* TIOCNOTTY */

		/* put in own process group */
		setpgid((pid_t) 0, getpid());

		path = getenv("PATH");
		*environ = NULL;
		m_putenv("USER", pw->pw_name);
		m_putenv("HOME", pw->pw_dir);
		m_putenv("SHELL", pw->pw_shell);
		m_putenv("PATH", path);

		execvp(pgm, vec);
		_exit(EX_OSERR);

	default:
		/* parent process */
		SIGNAL(SIGALRM, alrmser);
		bytes = fstat(fd, &st) != -1 ? (int) st.st_size : 100;

		/* amount of time to wait depends on message size */
		if (bytes <= 100) {
			/* give at least 5 minutes */
			seconds = 300;
		} else if (bytes >= 90000) {
			/* a half hour is long enough */
			seconds = 1800;
		} else {
			seconds = (bytes / 60) + 300;
		}
		alarm((unsigned int) seconds);
		status = pidwait(child_id, 0);
		alarm(0);

		if (eflag) {
			if (verbose) {
				verbose_printf(", timed-out; terminated\n");
			}
			return -1;
		}

		if (verbose) {
			if (!status) {
				verbose_printf(", success.\n");
			} else if ((status & 0xff00) == 0xff00) {
				verbose_printf(", system error\n");
			} else {
				pidstatus(status, stdout, ", failed");
			}
		}
		return (status == 0 ? 0 : -1);
	}
}


static void
alrmser(int i)
{
	eflag = 1;
	kill(-child_id, SIGKILL);
}


/*
** Get the `sender' from the envelope
** information ("From " line).
*/
static void
get_sender(char *envelope, char **sender)
{
	int i;
	unsigned char *cp;
	unsigned char buffer[BUFSIZ];

	if (!envelope) {
		*sender = mh_xstrdup("");
		return;
	}

	i = strlen("From ");
	strncpy(buffer, envelope + i, sizeof(buffer));
	buffer[sizeof buffer -1] = '\0';  /* ensure termination */
	if ((cp = strchr(buffer, '\n'))) {
		*cp = '\0';
		cp -= 24;
		if (cp < buffer) {
			cp = buffer;
		}
	} else {
		cp = buffer;
	}
	*cp = '\0';

	for (cp = buffer + strlen(buffer) - 1; cp >= buffer; cp--)
		if (isspace(*cp)) {
			*cp = '\0';
		} else {
			break;
		}
	*sender = mh_xstrdup(buffer);
}


/*
** Copy message into a temporary file.
** While copying, it will do some header processing
** including the extraction of the envelope information.
*/
static int
copy_message(int qd, char *tmpfil, int fold)
{
	int i, first = 1, fd1, fd2;
	char buffer[BUFSIZ];
	FILE *qfp, *ffp;
	char *tfile = NULL;

	tfile = m_mktemp2("/tmp/", invo_name, &fd1, NULL);
	if (tfile == NULL) return -1;
	fchmod(fd1, 0600);
	strncpy(tmpfil, tfile, BUFSIZ);

	if (!fold) {
		while ((i = read(qd, buffer, sizeof(buffer))) > 0) {
			if (write(fd1, buffer, i) != i) {
you_lose:
				close(fd1);
				unlink(tmpfil);
				return -1;
			}
		}
		if (i == -1) {
			goto you_lose;
		}
		lseek(fd1, (off_t) 0, SEEK_SET);
		return fd1;
	}

	/* dup the fd for incoming message */
	if ((fd2 = dup(qd)) == -1) {
		close(fd1);
		return -1;
	}

	/* now create a FILE pointer for it */
	if (!(qfp = fdopen(fd2, "r"))) {
		close(fd1);
		close(fd2);
		return -1;
	}

	/* dup the fd for temporary file */
	if ((fd2 = dup(fd1)) == -1) {
		close(fd1);
		fclose(qfp);
		return -1;
	}

	/* now create a FILE pointer for it */
	if (!(ffp = fdopen(fd2, "r+"))) {
		close(fd1);
		close(fd2);
		fclose(qfp);
		return -1;
	}

	/*
	** copy message into temporary file
	** and massage the headers.  Save
	** a copy of the "From " line for later.
	*/
	i = strlen("From ");
	while (fgets(buffer, sizeof(buffer), qfp)) {
		if (first) {
			first = 0;
			if (strncmp(buffer, "From ", i)==0) {
				/*
				** get copy of envelope information
				** ("From " line)
				*/
				envelope = mh_xstrdup(buffer);

				/* Put the delivery date in message */
				fputs(ddate, ffp);
				if (ferror(ffp)) {
					goto fputs_error;
				}
				continue;
			}
		}

		fputs(buffer, ffp);
		if (ferror(ffp)) {
			goto fputs_error;
		}
	}

	fclose(ffp);
	if (ferror(qfp)) {
		close(fd1);
		fclose(qfp);
		return -1;
	}
	fclose(qfp);
	lseek(fd1, (off_t) 0, SEEK_SET);
	return fd1;

fputs_error:
	close(fd1);
	fclose(ffp);
	fclose(qfp);
	return -1;
}

/*
** Trim strings for pretty printing of debugging output
*/
static char *
trimstr(char *cp)
{
	char buffer[BUFSIZ*4];
	unsigned char *bp, *sp;

	if (!cp) {
		return NULL;
	}

	/* copy string into temp buffer */
	strncpy(buffer, cp, sizeof(buffer));
	bp = buffer;

	/* skip over leading whitespace */
	while (isspace(*bp)) {
		bp++;
	}
	/* start at the end and zap trailing whitespace */
	for (sp = bp + strlen(bp) - 1; sp >= bp; sp--) {
		if (isspace(*sp)) {
			*sp = '\0';
		} else {
			break;
		}
	}

	/* replace remaining whitespace with spaces */
	for (sp = bp; *sp; sp++) {
		if (isspace(*sp)) {
			*sp = ' ';
		}
	}
	return mh_xstrdup(bp);
}

/*
** Function for printing `verbose' messages.
*/
static void
verbose_printf(char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vfprintf(stdout, fmt, ap);
	va_end(ap);
	fflush(stdout);
}


/*
** Function for printing `verbose' delivery
** error messages.
*/
static void
adorn(char *what, char *fmt, ...)
{
	va_list ap;
	int eindex;
	char *s;

	eindex = errno;  /* save the errno */
	fprintf(stdout, ", ");

	va_start(ap, fmt);
	vfprintf(stdout, fmt, ap);
	va_end(ap);

	if (what) {
		if (*what) {
			fprintf(stdout, " %s: ", what);
		}
		if ((s = strerror(eindex))) {
			fprintf(stdout, "%s", s);
		} else {
			fprintf(stdout, "Error %d", eindex);
		}
	}

	fputc('\n', stdout);
	fflush(stdout);
}


/*
** Function for printing `debug' messages.
*/
static void
debug_printf(char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
}
