
/*
 * umhook.c -- one attempt at a rcvmail hook for UUCP mail
 *
 * $Id$
 */

/* I don't comment my code heavily, so read this...

    You run this program from your .login file.  The invocation is simply
    "umhook".  The program "detaches" itself and runs unattended until you
    logout.  Whenever you get UUCP mail (or upto a minute afterwards),
    umhook will filter your UUCP mail drop to a temporary file.  The mail
    drop is *NOT* touched beyond this (even the access time remains the
    same).  For each message that was new in the mail drop, umhook will
    fork a process to interpret your .maildelivery file.

    The umhook program uses the -ljobs control facility to do two things:
	- determine when the controlling tty has gone away
	- kill a child that's run away (the child sets up a process group)
 */

#include <h/mh.h>
#include <zotnet/mf.h>
#include <zotnet/mts.h>
#include <pwd.h>
#include <signal.h>
#include <sys/ioctl.h>

static struct swit switches[] = {
#define	SLEEPSW	0
    "sleep seconds", 0,
#define VERSIONSW 1
    "version", 0,
#define	HELPSW	2
    "help", 4,
    NULL, NULL
};

static int  snooze = 60;

static int  uucp = NOTOK;

extern char *environ;

static char myhome[BUFSIZ] = "";
static char mymail[BUFSIZ] = "";
static char myaddr[BUFSIZ] = "";
static char mystat[BUFSIZ] = "";
static char myuser[BUFSIZ] = "";

int sigser ();


main (int argc, char **argv)
{
    char   *cp,
          **ap,
          **argp,
            buf[100],
           *arguments[MAXARGS];
    struct passwd  *pw;

#ifdef LOCALE
	setlocale(LC_ALL, "");
#endif
    invo_name = r1bindex (argv[0], '/');
    mts_init (invo_name);
    if ((cp = m_find (invo_name)) != NULL) {
	ap = brkstring (cp = getcpy (cp), " ", "\n");
	ap = copyip (ap, arguments);
    }
    else
	ap = arguments;
    copyip (argv + 1, ap);
    argp = arguments;

    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]", invo_name);
		    print_help (buf, switches);
		    done (1);
		case VERSIONSW:
		    print_version(invo_name);
		    done (1);

		case SLEEPSW: 
		    if (!(cp = *argp++) || *cp == '-')
			adios (NULL, "missing argument to %s", argp[-2]);
		    if ((snooze = atoi (cp)) < 0)
			adios (NULL, "bad argument %s %s", argp[-2], cp);
		    continue;
	    }
	adios (NULL, "usage: %s [switches]", invo_name);
    }

    if ((pw = getpwuid (getuid ())) == NULL)
	adios (NULL, "you lose big");

    *environ = NULL;
    m_putenv ("USER", pw->pw_name);
    m_putenv ("HOME", pw->pw_dir);
    m_putenv ("SHELL", pw->pw_shell);
    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);
    }

    sprintf (mymail, "%s/%s",
	    uucpldir[0] ? uucpldir : pw->pw_dir,
	    uucplfil[0] ? uucplfil : pw->pw_name);
    strcpy (myuser, pw->pw_name);
    sprintf (myaddr, "%s@%s", pw->pw_name, LocalName ());
    strcpy (myhome, pw->pw_dir);
    sprintf (mystat, ".%s_%d", invo_name, pw->pw_uid);

    if (access (slocalproc, X_OK) == NOTOK)
	adios (slocalproc, "unable to execute");

    closefds (fileno (stderr) + 1);

    SIGNAL (SIGINT, SIG_IGN);
    SIGNAL (SIGHUP, sigser);
    SIGNAL (SIGQUIT, SIG_IGN);
    SIGNAL (SIGTERM, sigser);

    switch (fork ()) {
	case NOTOK: 
	case OK: 
	    umhook ();
	    break;

	default: 
	    break;
    }

    exit (0);
}

/*  */

#ifndef	TIOCGPGRP
#define	pgrp_ok(pg)	1
#else	TIOCGPGRP
#define	pgrp_ok(pg)	(ioctl (2, TIOCGPGRP, (char *) &pg) != NOTOK)
#endif	TIOCGPGRP

static  umhook () {
    int     pg;
    struct stat st1,
                st2;

    st_init (&st1);

    for (; pgrp_ok (pg);) {
	if (stat (mymail, &st2) == NOTOK) {
	    st2.st_ino = (ino_t) 0;
	    st2.st_size = (off_t) 0;
	    st2.st_mtime = (time_t) 0;
	}
	else
	    if (st1.st_mtime != st2.st_mtime)
		if (st1.st_ino != st2.st_ino)
		    process ((off_t) 0, &st2);
		else
		    if (st1.st_size < st2.st_size)
			process (st1.st_size, &st2);

	st1.st_ino = st2.st_ino;
	st1.st_size = st2.st_size;
	st1.st_mtime = st2.st_mtime;

	sleep ((unsigned) snooze);
    }
}

/*  */

static  process (offset, st)
off_t offset;
struct stat *st;
{
    int	    td1,
            td2;
    time_t timep[2];
    char    tmpfil[BUFSIZ];
    register FILE *fp;

    if ((uucp = lkopen (mymail, 0)) == NOTOK)
	adios (NULL, "unable to lock and open %s", mymail);
    if (lseek (uucp, (off_t) offset, 0) == (off_t) NOTOK)
	adios (mymail, "unable to position to %ld offset on", (long) offset);

    strcpy (tmpfil, m_tmpfil (invo_name));
    if ((td1 = creat (tmpfil, TMPMODE)) == NOTOK)
	adios (tmpfil, "unable to create");
    close (td1);

    if ((td1 = open (tmpfil, 2)) == NOTOK)
	adios (tmpfil, "unable to open");
    unlink (tmpfil);
    if ((td2 = dup (td1)) == NOTOK)
	adios ("file descriptor", "unable to dup");

    switch (uucp2mmdf (uucp, td1, FALSE)) {
	case MFPRM: 
	    adios (NULL, "internal error while filtering UUCP mail");

	case MFSIO: 
	    adios (NULL, "no free file pointers");

	case MFERR: 
	    adios ("UUCP mail", "i/o error while filtering");

	case MFOK: 
	case MFROM: 
	case MFHDR: 
	case MFTXT: 
	    timep[0] = st->st_atime;
	    timep[1] = st->st_mtime;
	    utime (mymail, timep);
	    st_update (st);
	    break;
    }
    lkclose (uucp, mymail), uucp = NOTOK;

/*  */

    close (td1);

    lseek (td2, (off_t)0, 0);
    if ((fp = fdopen (td2, "r")) == NULL)
	adios (NULL, "no free file pointers");

    while (hook (fp))
	continue;
    fclose (fp);
}

/*  */

static int  hook (in)
register FILE *in;
{
    int     child_id,
            done,
            fd1,
            fd2,
            i;
    char    buffer[BUFSIZ],
            mysndr[BUFSIZ],
            myfile[BUFSIZ];
    register FILE *out;

    if (fgets (buffer, sizeof buffer, in) == NULL)
	return FALSE;

/* should insist on isdlm1 (buffer) here... */

    strcpy (myfile, m_tmpfil (invo_name));
    if ((fd1 = creat (myfile, TMPMODE)) == NOTOK)
	adios (myfile, "unable to create");
    close (fd1);

    if ((fd1 = open (myfile, 2)) == NOTOK)
	adios (myfile, "unable to open");
    unlink (myfile);
    if ((fd2 = dup (fd1)) == NOTOK)
	adios ("file descriptor", "unable to dup");

    if ((out = fdopen (fd1, "w")) == NULL)
	adios (NULL, "no free file pointers");

    for (done = TRUE;;) {
	if (fgets (buffer, sizeof buffer, in) == NULL)
	    break;		/* should be error */
	if (done && isdlm2 (buffer))
	    break;
	done = buffer[strlen (buffer) - 1] == '\n';
	fputs (buffer, out);
    }
    fclose (out);

    lseek (fd2, (off_t)0, 0);
    seeksndr (fd2, mysndr);

/*  */

    switch (child_id = fork ()) {
	case NOTOK: 
	    adios ("fork", "unable to");/* NOTREACHED */

	case OK: 
	    lseek (fd2, (off_t)0, 0);
	    if (fd2 != 0)
		dup2 (fd2, 0);
	    freopen ("/dev/null", "w", stdout);
	    freopen ("/dev/null", "w", stderr);
	    if (fd2 != 3)
		dup2 (fd2, 3);
	    closefds (4);
#ifdef	TIOCNOTTY
	    if ((i = open ("/dev/tty", 2)) != NOTOK) {
		ioctl (i, TIOCNOTTY, NULL);
		close (i);
	    }
#endif	TIOCNOTTY
#ifdef	BSD42
	    setpgrp (0, getpid ());
#endif	BSD42

	    execlp (slocalproc, r1bindex (slocalproc, '/'),
		    "-file", myfile, "-mailbox", mymail,
		    "-home", myhome, "-addr", myaddr,
		    "-user", myuser, "-sender", mysndr, NULL);
	    adios (slocalproc, "unable to exec");/* NOTREACHED */

	default: 
	    close (fd2);
	    pidwait (child_id, OK);
	    return TRUE;
    }
}

/*  */

static  seeksndr (fd1, mysndr)
int     fd1;
char   *mysndr;
{
    int     fd2;
    char   *bp,
           *hp,
            from[BUFSIZ],
            sender[BUFSIZ];
    register FILE *in;

    if ((fd2 = dup (fd1)) == NOTOK)
	adios ("file descriptor", "unable to dup");
    if ((in = fdopen (fd2, "r")) == NULL)
	adios (NULL, "no free file pointers");

    for (from[0] = sender[0] = NULL; mfgets (in, &hp) != DONE;)
	if ((bp = strchr (hp, ':')) != NULL) {
	    *bp++ = NULL;
	    if (lequal (hp, "From"))
		seekaddr (from, bp);
	    else
		if (lequal (hp, "Sender"))
		    seekaddr (sender, bp);
	}
    fclose (in);

    strcpy (mysndr, sender[0] ? sender : from[0] ? from : myaddr);
}

/*  */

static  seekaddr (addr, bp)
char   *addr,
       *bp;
{
    struct adrx *adrxp;

    if ((adrxp = seekadrx (bp)) == NULL)
	return;
    if (adrxp->err || !adrxp->mbox)
	return;

    if (adrxp->host)
	sprintf (addr, "%s@%s", adrxp->mbox, adrxp->host);
    else
	strcpy (addr, adrxp->mbox);

    while (seekadrx (NULL))
	continue;
}

/*  */

static st_init(st)
struct stat *st;
{
    int     fd;

    if ((fd = open (mystat, 0)) == NOTOK
	    || read (fd, (char *) st, sizeof *st) != (sizeof *st)) {
	st->st_ino = (ino_t) 0;
	st->st_size = (off_t) 0;
	st->st_mtime = (time_t) 0;
    }
    if (fd != NOTOK)
	close (fd);
}


static st_update(st)
struct stat *st;
{
    static int  fd = NOTOK;

    if (fd == NOTOK
	    && (fd = creat (mystat, TMPMODE)) == NOTOK)
	adios (mystat, "unable to write");

    lseek (fd, (off_t)0, 0);
    if (write (fd, (char *) st, sizeof *st) != (sizeof *st))
	adios (mystat, "error writing");
}


static int  sigser (sig)
int     sig;
{
#ifndef RELIABLE_SIGNALS
    SIGNAL (sig, SIG_IGN);
#endif

    done (1);
}


void	done (status)
int	status;
{
    lkclose (uucp, mymail), uucp = NOTOK;
    exit (status);
}
