
/*
 * slocal.c -- asynchronously filter and deliver new mail
 *
 * $Id$
 */

/*
 *  This program implements mail delivery in the MH/MMDF style.
 *
 *  Under sendmail, users should add the line
 *
 * 	"| /usr/local/nmh/lib/slocal"
 *
 *  to their $HOME/.forward file.
 *
 *  Under MMDF-I, users should (symbolically) link
 *  /usr/local/nmh/lib/slocal to $HOME/bin/rcvmail.
 *
 *  Under stand-alone MH, post will automatically run this during local
 *  delivery.
 */

#include <h/mh.h>
#include <h/dropsbr.h>
#include <h/rcvmail.h>
#include <h/signals.h>
#include <zotnet/tws/tws.h>
#include <zotnet/mts/mts.h>

#include <pwd.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <utmp.h>
#include <ndbm.h>
#include <fcntl.h>

static struct swit switches[] = {
#define	ADDRSW         0
    { "addr address", 0 },
#define	USERSW         1
    { "user name", 0 },
#define	FILESW         2
    { "file file", 0 },
#define	SENDSW         3
    { "sender address", 0 },
#define	MBOXSW         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", 0 },
#define SUPPRESSDUP   10
    { "suppressdup", 0 },
#define NSUPPRESSDUP 11
    { "nosuppressdup", 0 },
#define	DEBUGSW       12
    { "debug", 0 },
#define VERSIONSW     13
    { "version", 0 },
#define	HELPSW        14
    { "help", 4 },
    { NULL, 0 }
};

static int globbed = 0;
static int parsed = 0;
static int utmped = 0;
static int suppressdup = 0;   /* are we suppressing duplicate messages? */
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 *unixfrom = NULL;
static char *mbox = NULL;
static char *home = NULL;

static struct passwd *pw;

static char ddate[BUFSIZ];

struct tws *now;

static jmp_buf myctx;

/* flags for pair->p_flags */
#define	P_NIL  0x00
#define	P_ADR  0x01
#define	P_HID  0x02
#define	P_CHK  0x04

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

#define	NVEC 100

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, NULL }
};

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, NULL }
};

extern char **environ;

/*
 * static prototypes
 */
static int localmail (int, char *, char *);
static int usr_delivery (int, char *, int, char *);
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 logged_in (void);
static int timely (char *, char *);
static int usr_file (int, char *, char *);
static int usr_pipe (int, char *, char *, char **);
static RETSIGTYPE alrmser (int);
static void copyinfo (FILE *fp, char *);
static int copyfile (int, char *, int);
static void adorn (char *, char *, ...);
static int suppress_duplicates (int, char *);


main (int argc, char **argv)
{
    int fd, result;
    FILE *fp = stdin;
    char *cp, *mdlvr = NULL, buf[100];
    char from[BUFSIZ], mailbox[BUFSIZ];
    char tmpfil[BUFSIZ], **argp = argv + 1;

#ifdef LOCALE
	setlocale(LC_ALL, "");
#endif
    invo_name = r1bindex (*argv, '/');
    m_foil (NULL);
    mts_init (invo_name);

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

		case HELPSW: 
		    sprintf (buf, "%s [switches] [address info sender]", invo_name);
		    print_help (buf, switches);
		    done (1);
		case VERSIONSW:
		    print_version(invo_name);
		    done (1);

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

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

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

		case SUPPRESSDUP:
		    suppressdup++;
		    continue;
		case NSUPPRESSDUP:
		    suppressdup = 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 == NULL)
	addr = getusr ();
    if (user == NULL)
	user = (cp = strchr(addr, '.')) ? ++cp : addr;
    if ((pw = getpwnam (user)) == NULL)
	adios (NULL, "no such local user as %s", user);

    if (chdir (pw->pw_dir) == NOTOK)
	chdir ("/");
    umask (0077);

    if (geteuid() == 0) {

#ifdef	BSD41A
	inigrp (pw->pw_name, pw->pw_gid);
#endif	/* BSD41A */

	setgid (pw->pw_gid);

#ifdef	BSD42
	initgroups (pw->pw_name, pw->pw_gid);
#endif	/* BSD42 */

	setuid (pw->pw_uid);
    }
    
    if (info == NULL)
	info = "";

    setbuf (stdin, NULL);

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

	if ((tempfd = open (file, O_RDONLY)) == -1)
	    adios(file, "unable to open");
	if (debug)
	    fprintf (stderr, "retrieving message from file \"%s\"\n", file);
	if ((fd = copyfile (tempfd, tmpfil, 1)) == -1)
	    adios (NULL, "unable to create temporary file");
	close (tempfd);
    } else {
	/* getting message from stdin */
	if (debug)
	    fprintf(stderr, "retrieving message from stdin\n");
	if ((fd = copyfile (fileno (stdin), tmpfil, 1)) == -1)
	    adios (NULL, "unable to create temporary file");
    }
    if (debug)
	fprintf (stderr, "temporary file=\"%s\"\n", tmpfil);
    else
	unlink (tmpfil);
    if (!(fp = fdopen (fd, "r+")))
	adios (NULL, "unable to access temporary file");

    from[0] = 0;
    if (sender == NULL)
	copyinfo (fp, from);

    if (mbox == NULL) {
	sprintf (mailbox, "%s/%s",
		mmdfldir[0] ? mmdfldir : pw->pw_dir,
		mmdflfil[0] ? mmdflfil : pw->pw_name);
	mbox = mailbox;
    }
    if (home == NULL)
	home = pw->pw_dir;

    if ((now = dtwstime ()) == NULL)
	adios (NULL, "unable to ascertain local time");
    sprintf (ddate, "Delivery-Date: %s\n", dtimenow ());

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

    result = localmail (fd, from, mdlvr);
    done (result != NOTOK ? RCV_MOK : RCV_MBX);
}


static int
localmail (int fd, char *from, char *mdlvr)
{
    if (suppressdup && suppress_duplicates(fd, ".maildelivery") == DONE)
	return OK;

    if (usr_delivery (fd, mdlvr ? mdlvr : ".maildelivery", 0, from) != NOTOK)
	return OK;

    if (usr_delivery (fd, maildelivery, 1, from) != NOTOK)
	return OK;

#if 0
    if (verbose)
	printf ("(invoking hook)\n");
    if (usr_hook (fd, mbox) != NOTOK)
	return OK;
#endif

    if (verbose)
	printf ("(trying normal delivery)\n");
    return usr_file (fd, mbox, from);
}


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

static int
usr_delivery (int fd, char *delivery, int su, char *from)
{
    int i, accept, status, won, vecp, next;
    register char *cp, *action, *field, *pattern, *string;
    char buffer[BUFSIZ], tmpbuf[BUFSIZ], *vec[NVEC];
    struct stat st;
    register struct pair *p;
    register FILE *fp;

    if ((fp = fopen (delivery, "r")) == NULL)
	return NOTOK;
    if (fstat (fileno (fp), &st) == NOTOK
	    || (st.st_uid != 0 && (su || st.st_uid != pw->pw_uid))
	    || st.st_mode & (S_IWGRP|S_IWOTH)) {
	if (verbose) {
	    printf ("%s: ownership/modes bad (%d, %d,%d,0%o)\n",
		    delivery, su, pw->pw_uid, st.st_uid, st.st_mode);
	    fflush (stdout);
	}
	return NOTOK;
    }

    won = 0;
    next = 1;
    while (fgets (buffer, sizeof(buffer), fp)) {
	/* skip comments and empty lines */
	if (*buffer == '#' || *buffer == '\n')
	    continue;
	if ((cp = strchr(buffer, '\n')))
	    *cp = 0;
	if ((vecp = split (buffer, vec)) < 5) {
	    if (debug)
		fprintf (stderr, "WARNING: entry with only %d fields, skipping ...\n", vecp);
	    continue;
	}
	if (debug) {
	    for (i = 0; vec[i]; i++)
		fprintf (stderr, "vec[%d]: \"%s\" ", i, vec[i]);
	    fprintf (stderr, "\n");
	}

	field   = vec[0];
	pattern = vec[1];
	action  = vec[2];

	switch (vec[3][0]) {
	    case 'N':
	    case 'n':
 		if (!next)
		    continue;  /* if previous condition failed, don't
				  do this - else fall through */
	    case '?': 
		if (won)
		    continue;	/* else fall */
	    case 'A': 
	    case 'a': 
		accept = 1;
		break;

	    case 'R': 
	    case 'r': 
	    default: 
		accept = 0;
		break;
	}

	string = vec[4];

	if (vecp > 5) {
	    if (uleq (vec[5], "select")) {
		if (logged_in () != NOTOK)
		    continue;
		if (vecp > 7 && timely (vec[6], vec[7]) == NOTOK)
		    continue;
	    }
	}

	switch (*field) {
	    case '*': 
		break;

	    case 'd': 
		if (uleq (field, "default")) {
		    if (won)
			continue;
		    break;
		}		/* else fall */

	    default: 
		if (!parsed && parse (fd) == NOTOK) {
		    fclose (fp);
		    return NOTOK;
		}
		if ((p = lookup (hdrs, field)) == NULL
			|| (p->p_value == NULL)			/* XXX */
			|| !matches (p->p_value, pattern)) {
			next = 0;
			continue;
		}
		else
		    next = 1;
		break;
	}

	switch (*action) {
	    case 'q':
		if (!uleq (action, "qpipe"))
		    continue;	/* else fall */
	    case '^':
		expand (tmpbuf, string, fd);
		if (split (tmpbuf, vec) < 1)
		    continue;
		status = usr_pipe (fd, tmpbuf, vec[0], vec);
		break;

	    case 'p': 
		if (!uleq (action, "pipe"))
		    continue;	/* else 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);
		break;

	    case '+':
	    dofolder:
		if (*string == '+')
		    strcpy(tmpbuf, string);
		else
		    sprintf(tmpbuf, "+%s", string);
		vec[2] = "rcvstore";
		vec[3] = tmpbuf;
		vec[4] = NULL;
		if (verbose)
		    printf("%s %s", rcvstoreproc, vec[3]);
		status = usr_pipe(fd, "rcvstore", rcvstoreproc, vec + 2);
		break;

	    case 'f': 
		if (uleq(action, "folder"))
		    goto dofolder;
		if (!uleq (action, "file"))
		    continue;	/* else fall */
	    case '>': 
#ifdef RPATHS
		status = usr_file (fd, string,  from);	/* UUCP format? */
#else
		status = usr_file (fd, string,  NULL);
#endif
		break;

	    case 'm':
		if (!uleq (action, "mbox"))
		    continue;
		status = usr_file (fd, string,  NULL);
		break;

	    case 'd': 
		if (!uleq (action, "destroy"))
		    continue;
		status = OK;
		break;
	}

	if (accept && status == OK)
	    won++;
    }

    fclose (fp);
    return (won ? OK : NOTOK);
}


#define	QUOTE	'\\'

static int
split (char *cp, char **vec)
{
    register int i;
    register char *s;

    s = cp;
    for (i = 0; i <= NVEC;) {
	vec[i] = NULL;
	while (isspace (*s) || *s == ',')
	    *s++ = 0;
	if (*s == 0)
	    break;

	if (*s == '"') {
	    for (vec[i++] = ++s; *s && *s != '"'; s++)
		if (*s == QUOTE) {
		    if (*++s == '"')
			strcpy (s - 1, s);
		    s--;
		}
	    if (*s == '"')
		*s++ = 0;
	    continue;
	}
	if (*s == QUOTE && *++s != '"')
	    s--;
	vec[i++] = s++;

	while (*s && !isspace (*s) && *s != ',')
	    s++;
    }
    vec[i] = NULL;

    return i;
}


static int
parse (int fd)
{
    register int i, state;
    int fd1;
    register char *cp, *dp, *lp;
    char name[NAMESZ], field[BUFSIZ];
    register struct pair *p, *q;
    register FILE  *in;

    if (parsed++)
	return OK;

    if ((fd1 = dup (fd)) == NOTOK)
	return NOTOK;
    if ((in = fdopen (fd1, "r")) == NULL) {
	close (fd1);
	return NOTOK;
    }
    rewind (in);

    if ((p = lookup (hdrs, "source")))
	p->p_value = getcpy (sender);
    if ((p = lookup (hdrs, "addr")))
	p->p_value = getcpy (addr);

    for (i = 0, state = FLD;;) {
	switch (state = m_getfld (state, name, field, sizeof field, in)) {
	    case FLD: 
	    case FLDEOF: 
	    case FLDPLUS: 
		lp = add (field, NULL);
		while (state == FLDPLUS) {
		    state = m_getfld (state, name, field, sizeof field, in);
		    lp = add (field, lp);
		}
		for (p = hdrs; p->p_name; p++)
		    if (uleq (p->p_name, name)) {
			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);
			}
			free (lp);
			break;
		    }
		if (p->p_name == NULL && i < NVEC) {
		    p->p_name = getcpy (name);
		    p->p_value = lp;
		    p->p_flags = P_NIL;
		    p++, i++;
		    p->p_name = NULL;
		}
		if (state != FLDEOF)
		    continue;
		break;

	    case BODY: 
	    case BODYEOF: 
	    case FILEEOF: 
		break;

	    case LENERR: 
	    case FMTERR: 
		advise (NULL, "format error in message");
		break;

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

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

    return OK;
}


#define	LPAREN	'('
#define	RPAREN	')'

static void
expand (char *s1, char *s2, int fd)
{
    register char c, *cp;
    register struct pair *p;

    if (!globbed)
	glob (fd);

    while ((c = *s2++))
	if (c != '$' || *s2 != LPAREN)
	    *s1++ = c;
	else {
	    for (cp = ++s2; *s2 && *s2 != RPAREN; s2++)
		continue;
	    if (*s2 != RPAREN) {
		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;
}


static void
glob (int fd)
{
    char buffer[BUFSIZ];
    struct stat st;
    register struct pair *p;

    if (globbed++)
	return;

    if ((p = lookup (vars, "sender")))
	p->p_value = getcpy (sender);
    if ((p = lookup (vars, "address")))
	p->p_value = getcpy (addr);
    if ((p = lookup (vars, "size"))) {
	sprintf (buffer, "%d", fstat (fd, &st) != NOTOK ? (int) st.st_size : 0);
	p->p_value = getcpy (buffer);
    }
    if ((p = lookup (vars, "info")))
	p->p_value = getcpy (info);

    if (debug)
	for (p = vars; p->p_name; p++)
	    fprintf (stderr, "vars[%d]: name=\"%s\" value=\"%s\"\n",
		    p - vars, p->p_name, p->p_value);
}


static struct pair *
lookup (struct pair *pairs, char *key)
{
    for (; pairs->p_name; pairs++)
	if (uleq (pairs->p_name, key))
	    return pairs;

    return NULL;
}


static int
logged_in (void)
{
    struct utmp ut;
    register FILE *uf;

    if (utmped)
	return utmped;

    if ((uf = fopen ("/etc/utmp", "r")) == NULL)
	return NOTOK;

    while (fread ((char *) &ut, sizeof ut, 1, uf) == 1)
	if (ut.ut_name[0] != 0
		&& strncmp (user, ut.ut_name, sizeof ut.ut_name) == 0) {
	    if (debug)
		continue;
	    fclose (uf);
	    return (utmped = DONE);
	}

    fclose (uf);
    return (utmped = NOTOK);
}


#define	check(t,a,b)		if (t < a || t > b) return NOTOK
#define	cmpar(h1,m1,h2,m2)	if (h1 < h2 || (h1 == h2 && m1 < m2)) return OK

static int
timely (char *t1, char *t2)
{
    int t1hours, t1mins, t2hours, t2mins;

    if (sscanf (t1, "%d:%d", &t1hours, &t1mins) != 2)
	return NOTOK;
    check (t1hours, 0, 23);
    check (t1mins, 0, 59);

    if (sscanf (t2, "%d:%d", &t2hours, &t2mins) != 2)
	return NOTOK;
    check (t2hours, 0, 23);
    check (t2mins, 0, 59);

    cmpar (now->tw_hour, now->tw_min, t1hours, t1mins);
    cmpar (t2hours, t2mins, now->tw_hour, now->tw_min);

    return NOTOK;
}


static int
usr_file (int fd, char *mailbox, char *from)
{
    int	md, mapping;
    register char *bp;
    char buffer[BUFSIZ];

    if (verbose)
	printf ("\tdelivering to file \"%s\"", mailbox);
    if (from && *from) {
	mbx_uucp ();
	if (verbose)
	    printf (" (uucp style)");
	sprintf (buffer, "%s%s", from, ddate);
	bp = buffer;
	mapping = 0;
    }
    else {
	bp = ddate;
	mapping = 1;
    }
    if (verbose)
	fflush (stdout);

    if ((md = mbx_open (mailbox, pw->pw_uid, pw->pw_gid, m_gmprot ()))
	    == NOTOK) {
	adorn ("", "unable to open:");
	return NOTOK;
    }

    lseek (fd, (off_t) 0, 0);
    if (mbx_copy (mailbox, md, fd, mapping, bp, verbose) == NOTOK) {
	adorn ("", "error writing to:");
	return NOTOK;
    }

    mbx_close (mailbox, md);
    if (verbose) {
	printf (", done.\n");
	fflush (stdout);
    }
    return OK;
}


#if 0
static int
usr_hook (int fd, char *mailbox)
{
    int i, vecp;
    char receive[BUFSIZ], tmpfil[BUFSIZ], *vec[NVEC];

    if ((fd = copyfile (fd, tmpfil, 0)) == NOTOK) {
	if (verbose)
	    adorn ("unable to copy message; skipping hook\n");
	return NOTOK;
    }
    chown (tmpfil, pw->pw_uid, pw->pw_gid);

    vecp = 1;
    sprintf (receive, "%s/.mh_receive", pw->pw_dir);
    switch (access (receive, X_OK)) {
	case NOTOK: 
	    sprintf (receive, "%s/bin/rcvmail", pw->pw_dir);
	    if (access (receive, X_OK) == NOTOK) {
		unlink (tmpfil);
		if (verbose) {
		    printf ("\tnot present\n");
		    fflush (stdout);
		}
		return NOTOK;
	    }
	    vec[vecp++] = addr;
	    vec[vecp++] = tmpfil;
	    vec[vecp++] = sender;
	    break;

	default: 
	    vec[vecp++] = tmpfil;
	    vec[vecp++] = mailbox;
	    vec[vecp++] = home;
	    vec[vecp++] = addr;
	    vec[vecp++] = sender;
	    break;
    }
    vec[0] = r1bindex (receive, '/');
    vec[vecp] = NULL;

    i = usr_pipe (fd, "rcvmail", receive, vec);
    unlink (tmpfil);

    return i;
}
#endif


static int
usr_pipe (int fd, char *cmd, char *pgm, char **vec)
{
    pid_t child_id;
    int bytes, i, status;
    struct stat st;

    if (verbose) {
	printf ("\tdelivering to pipe \"%s\"", cmd);
	fflush (stdout);
    }
    lseek (fd, (off_t) 0, 0);

    for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
	sleep (5);
    switch (child_id) {
	case NOTOK: 
	    adorn ("fork", "unable to");
	    return NOTOK;

	case OK: 
	    if (fd != 0)
		dup2 (fd, 0);
	    freopen ("/dev/null", "w", stdout);
	    freopen ("/dev/null", "w", stderr);
	    if (fd != 3)
		dup2 (fd, 3);
	    closefds (4);

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

#ifdef	BSD42
	    setpgrp (0, getpid ());
#endif	/* BSD42 */

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

	    execvp (pgm, vec);
	    _exit (-1);

	default: 
	    if (!setjmp (myctx)) {
		SIGNAL (SIGALRM, alrmser);
		bytes = fstat (fd, &st) != NOTOK ? (int) st.st_size : 100;
		if (bytes <= 0)
		    bytes = 100;

		alarm ((unsigned) (bytes * 60 + 300));
		status = pidwait (child_id, OK);
		alarm (0);
#ifdef MMDFI
		if (status == RP_MOK || status == RP_OK)
		    status = 0;
#endif
		if (verbose) {
		    if (status == 0)
			printf (", wins.\n");
		    else
			if ((status & 0xff00) == 0xff00)
			    printf (", system error\n");
			else
			    pidstatus (status, stdout, ", loses");
		    fflush (stdout);
		}
		return (status == 0 ? OK : NOTOK);
	    } else {
		KILLPG(child_id, SIGKILL);
		if (verbose) {
		    printf (", timed-out; terminated\n");
		    fflush (stdout);
		}
		return NOTOK;
	    }
    }
}


static RETSIGTYPE
alrmser (int i)
{
#ifndef RELIABLE_SIGNALS
    SIGNAL (SIGALRM, alrmser);
#endif

    longjmp (myctx, DONE);
}


static void
copyinfo (FILE *fp, char *from)
{
    int i;
    register char *cp;
    static char buffer[BUFSIZ];

    if (unixfrom)	/* interface from copyfile */
	strcpy (from, unixfrom);
    else if (fgets (from, BUFSIZ, fp) == NULL)
	adios (NULL, "no message");

    if (strncmp (from, "From ", i = strlen ("From "))) {
	rewind (fp);
	*from = 0;
	return;
    }

    strcpy (buffer, from + i);
    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 = buffer;
    rewind (fp);
}


static int
copyfile (int qd, char *tmpfil, int fold)
{
    register int i, first = 0, fd1, fd2;
    char buffer[BUFSIZ];
    register FILE *qfp, *ffp;

    strcpy (tmpfil, m_tmpfil (invo_name));
    if ((fd1 = open (tmpfil, O_RDWR | O_CREAT | O_TRUNC, 0600)) == NOTOK)
	return NOTOK;

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

    if ((fd2 = dup (qd)) == NOTOK) {
	close (fd1);
	return NOTOK;
    }
    if ((qfp = fdopen (fd2, "r")) == NULL) {
	close (fd1);
	close (fd2);
	return NOTOK;
    }

    if ((fd2 = dup (fd1)) == NOTOK) {
	close (fd1);
	fclose (qfp);
	return NOTOK;
    }
    if ((ffp = fdopen (fd2, "r+")) == NULL) {
	close (fd1);
	close (fd2);
	fclose (qfp);
	return NOTOK;
    }

    i = strlen ("From ");
    while (fgets (buffer, sizeof(buffer), qfp)) {
	if (!strncmp (buffer, "From ", i))
	    if (first == 0) {
#ifdef RPATHS
		register char *fp, *cp, *hp, *ep;
#endif
		unixfrom = getcpy (buffer);     /* save for later */
#ifndef RPATHS
		continue;                       /* but don't put in file */
#else
		hp = cp = strchr(fp = unixfrom + i, ' ');
		while ((hp = strchr(++hp, 'r')))
		    if (uprf (hp, "remote from")) {
			hp = strrchr(hp, ' ');
			break;
		    }
		if (hp) {
		    ep = strrchr(++hp, '\n');
		    sprintf (buffer, "Return-Path: %.*s!%.*s\n",
			    ep - hp, hp,
			    cp - fp, fp);
		}
		else
		    sprintf (buffer, "Return-Path: %.*s\n",
			    cp - fp, fp);
#endif
	    }
#ifdef	notdef		/* mbx_copy does this */
	    else
		putc ('>', ffp);
#endif	/* notdef */
	first++;
	fputs (buffer, ffp);
	if (ferror (ffp)) {
	    close (fd1);
	    fclose (ffp);
	    fclose (qfp);
	    return NOTOK;
	}
    }

    fclose (ffp);
    if (ferror (qfp)) {
	close (fd1);
	fclose (qfp);
	return NOTOK;
    }
    fclose (qfp);

    lseek (fd1, (off_t) 0, 0);

    return fd1;
}


static void
adorn (char *what, char *fmt, ...)
{
    va_list ap;
    char *cp;

    if (!verbose)
	return;
    printf (", ");

    cp = invo_name;
    invo_name = NULL;

    va_start(ap, fmt);
    advertise (what, NULL, fmt, ap);
    va_end(ap);

    invo_name = cp;
}


/*
 * Check ndbm/db file(s) to see if the Message-Id of this
 * message matches the Message-Id of a previous message,
 * so we can discard it.  If it doesn't match, we add the
 * Message-Id of this message to the ndbm/db file.
 */
static int
suppress_duplicates (int fd, char *file)
{
    int	fd1, state;
    char *cp, buf[BUFSIZ], name[NAMESZ];
    datum key, value;
    DBM *db;
    FILE *in;

    if ((fd1 = dup (fd)) == NOTOK)
	return NOTOK;
    if (!(in = fdopen (fd1, "r"))) {
	close (fd1);
	return NOTOK;
    }
    rewind (in);

    for (state = FLD;;) {
	state = m_getfld (state, name, buf, sizeof buf, in);
	switch (state) {
	    case FLD:
	    case FLDPLUS:
	    case FLDEOF:
		if (!uleq (name, "Message-ID")) {
		    while (state == FLDPLUS)
			state = m_getfld (state, name, buf, sizeof buf, in);
		    continue;
		}

		cp = add (buf, NULL);
		while (state == FLDPLUS) {
		    state = m_getfld (state, name, buf, sizeof buf, in);
		    cp = add (buf, cp);
		}
		key.dsize = strlen (key.dptr = trimcpy (cp)) + 1;
		free (cp);
		cp = key.dptr;

		if (!(db = dbm_open (file, O_RDWR | O_CREAT, 0600))) {
		    advise (file, "unable to perform dbm_open on");
		    free (cp);
		    fclose (in);
		    return NOTOK;
		}
		if (lockfile(dbm_pagfno(db)) == -1) {
		    advise (file, "unable to perform file locking on");
		    free (cp);
		    fclose (in);
		    return NOTOK;
		}

		value = dbm_fetch (db, key);
		if (value.dptr) {
		    if (verbose)
		        printf ("Message-ID: %s already received\non Date: %s", cp, value.dptr);
		    free (cp);
		    fclose (in);
		    return DONE;
		}
			   
		value.dptr  = ddate + sizeof("Delivery-Date:");
		value.dsize = strlen(value.dptr) + 1;

		if (dbm_store (db, key, value, DBM_INSERT))
		    advise (file, "possibly corrupt file");

		/*
		 * no need to unlock the dbm file(s), since we
		 * immediately close them anyway.
		 */
		dbm_close (db);

		free (cp);
		break;

	   case BODY:
	   case BODYEOF:
	   case FILEEOF:
		break;

	   case LENERR:
	   case FMTERR:
	   default:
		break;
	}

	break;
    }

    fclose (in);
    return OK;
}

