#ifndef lint
static char sccsid[] = "@(#)delivermail.c	4.36 (Berkeley) 4/21/89";
static char  rcsid[] = "@(#)$Id: binmail.c,v 4.36.0.2 1991/06/25 23:55:57 paul Exp $";
#endif

#include <sys/param.h>
#include <sys/stat.h>
#include <sys/file.h>

#include <ctype.h>
#include <stdio.h>
#include <pwd.h>
#include <utmp.h>
#include <signal.h>
#include <setjmp.h>
#include <sysexits.h>
#include "pathnames.h"

	/* copylet flags */
#define REMOTE		1		/* remote mail, add rmtmsg */
#define ORDINARY	2
#define ZAP		3		/* zap header and trailing empty line */
#define	FORWARD		4

#define	LSIZE		256
#define	MAXLET		300		/* maximum number of letters */
#define	MAILMODE	0600		/* mode of created mail */

char	line[LSIZE];
char	resp[LSIZE];
struct let {
	long	adr;
	char	change;
} let[MAXLET];
int	nlet	= 0;
char	lfil[50];
long	iop, time ();
char	*getenv ();
char	*index ();
char	lettmp[] = _PATH_TMP;
char	maildir[] = _PATH_MAILDIR;
char	mailfile[] = "/usr/spool/mail/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
char	dead[] = "dead.letter";
char	forwmsg[] = " forwarded\n";
FILE	*tmpf;
FILE	*malf;
char	my_name[60];
char	*getlogin ();
int	error;
int	changed;
int	forward;
char	from[] = "From ";
long	ftell ();
int	delex ();
char	*ctime ();
int	flgf;
int	flgp;
int	delflg = 1;
int	hseqno;
jmp_buf	sjbuf;
int	rmail;

main (argc, argv)
    int	    argc;
    char    **argv;
{
    register int    i;
    char	    *name;
    struct passwd   *pwent;

    if (! (name = getlogin ()) || ! *name
	|| ! (pwent = getpwnam (name)) || getuid () != pwent->pw_uid) 
	pwent = getpwuid (getuid ());

    strncpy (my_name, pwent ? pwent->pw_name : "???", sizeof (my_name)-1);

    if (setjmp (sjbuf))
	done ();

    for (i = SIGHUP; i <= SIGTERM; i++)
	setsig (i, delex);

    umask (077);

    i = mkstemp (lettmp);

    tmpf = fdopen (i, "r+");

    if (i < 0 || tmpf == NULL)
	panic ("mail: %s: cannot open for writing", lettmp);

	/*
	 * This protects against others reading mail from temp file and
	 * if we exit, the file will be deleted already.
	 */

    unlink (lettmp);

    if (argv[0][0] == 'r')
	rmail++;

    if ((argv[0][0] != 'r')		    /* no favors for rmail*/
	&& (argc == 1 || argv[1][0] == '-' && !any (argv[1][1], "rhd")))
	printmail (argc, argv);
    else
	bulkmail (argc, argv);

    done ();
}

setsig (i, f)
    int	    i;
    int	    (*f)();
{
    if (signal (i, SIG_IGN) != SIG_IGN)
	signal (i, f);
}

any (c, str)
    register int    c;
    register char   *str;
{
    while (*str)
	if (c == *str++)
	    return (1);

    return (0);
}

printmail (argc, argv)
    int	    argc;
    char    **argv;
{
    int		    flg, i, j, print;
    char	    *p, *getarg ();
    struct stat	    statb;
#if defined (MH_LCK_BEL)
    int		    lk_fd;
    extern int	    lkopen (), lkclose ();
#endif /* MH_LCK_BEL */

    setuid (getuid ());
    cat (mailfile, maildir, my_name);

#ifdef notdef
    if (stat (mailfile, &statb) >= 0
	&& (statb.st_mode & S_IFMT) == S_IFDIR) {
	strcat (mailfile, "/");
	strcat (mailfile, my_name);
    }
#endif

    for (; argc > 1; argv++, argc--) {
	if (argv[1][0] != '-')
	    break;

	switch (argv[1][1]) {
	case 'p':
	    flgp++;
	    /* fall thru... */

	case 'q':
	    delflg = 0;
	    break;

	case 'f':
	    if (argc >= 3) {
		strcpy (mailfile, argv[2]);
		argv++, argc--;
	    }

	    break;

	case 'b':
	    forward = 1;
	    break;

	default:
	    panic ("unknown option %c", argv[1][1]);
	    /*NOTREACHED*/
	}
    }

#if defined (LOCKF_FLOCK)
    /*
     *	We can't get a lock on a file that
     *	isn't opened for writing - sigh!
     */

    malf = fopen (mailfile, "r+");
#else
    malf = fopen (mailfile, "r");
#endif /* LOCKF_FLOCK */

    if (malf == NULL) {
	printf ("No mail.\n");

	return;
    }

#if defined (MH_LCK_BEL)
    lk_fd = lkopen (mailfile, O_RDONLY);
#endif /* MH_LCK_BEL */

    flock (fileno (malf), LOCK_SH);
    copymt (malf, tmpf);
    fclose (malf);			/* implicit unlock */

#if defined (MH_LCK_BEL)
    (void) lkclose (lk_fd, mailfile);
#endif /* MH_LCK_BEL */

    fseek (tmpf, 0L, L_SET);

    changed = 0;
    print = 1;

    for (i = 0; i < nlet; ) {
	j = forward ? i : nlet - i - 1;

	if (setjmp (sjbuf)) {
	    print = 0;
	}
	else {
	    if (print)
		copylet (j, stdout, ORDINARY);

	    print = 1;
	}

	if (flgp) {
	    i++;
	    continue;
	}

	setjmp (sjbuf);

	fputs ("? ", stdout);
	fflush (stdout);

	if (fgets (resp, LSIZE, stdin) == NULL)
	    break;

	switch (resp[0]) {

	default:
	    printf ("usage\n");

	case '?':
	    print = 0;
	    printf ("q\tquit\n");
	    printf ("x\texit without changing mail\n");
	    printf ("p\tprint\n");
	    printf ("s[file]\tsave (default mbox)\n");
	    printf ("w[file]\tsame without header\n");
	    printf ("-\tprint previous\n");
	    printf ("d\tdelete\n");
	    printf ("+\tnext (no delete)\n");
	    printf ("m user\tmail to user\n");
	    printf ("! cmd\texecute cmd\n");
	    break;

	case '+':
	case 'n':
	case '\n':
	    i++;
	    break;

	case 'x':
	    changed = 0;

	case 'q':
	    goto donep;

	case 'p':
	    break;

	case '^':
	case '-':
	    if (--i < 0)
		i = 0;
	    break;

	case 'y':
	case 'w':
	case 's':
	    flg = 0;

	    if (resp[1] != '\n' && resp[1] != ' ') {
		printf ("illegal\n");
		flg++;
		print = 0;
		continue;
	    }

	    if (resp[1] == '\n' || resp[1] == '\0') {
		p = getenv ("HOME");

		if (p != 0)
		    cat (resp+1, p, "/mbox");
		else
		    cat (resp+1, "", "mbox");
	    }

	    for (p = resp+1; (p = getarg (lfil, p)) != NULL; ) {
		malf = fopen (lfil, "a");

		if (malf == NULL) {
		    printf ("mail: %s: cannot append\n", lfil);
		    flg++;
		    continue;
		}

		copylet (j, malf, resp[0] == 'w'? ZAP: ORDINARY);
		fclose (malf);
	    }

	    if (flg)
		print = 0;
	    else {
		let[j].change = 'd';
		changed++;
		i++;
	    }

	    break;

	case 'm':
	    flg = 0;

	    if (resp[1] == '\n' || resp[1] == '\0') {
		i++;
		continue;
	    }

	    if (resp[1] != ' ') {
		printf ("invalid command\n");
		flg++;
		print = 0;
		continue;
	    }

	    for (p = resp+1; (p = getarg (lfil, p)) != NULL; )
		if (!sendmail (j, lfil, my_name))
		    flg++;

	    if (flg)
		print = 0;
	    else {
		let[j].change = 'd';
		changed++;
		i++;
	    }

	    break;

	case '!':
	    system (resp+1);
	    printf ("!\n");
	    print = 0;
	    break;

	case 'd':
	    let[j].change = 'd';
	    changed++;
	    i++;

	    if (resp[1] == 'q')
		goto donep;

	    break;
	}
    }

donep:
    if (changed)
	copyback ();
}

/* copy temp or whatever back to /usr/spool/mail */

copyback ()
{
    register int    i, c;
    long	    oldmask;
    int		    fd, new = 0;
    struct stat	    stbuf;
#if defined (MH_LCK_BEL)
    int		    lk_fd;
    extern int	    lkopen (), lkclose ();
#endif /* MH_LCK_BEL */

    oldmask = sigblock (sigmask (SIGINT)|sigmask (SIGHUP)|sigmask (SIGQUIT));
    fd = open (mailfile, O_RDWR | O_CREAT, MAILMODE);

    if (fd >= 0) {
#if defined (MH_LCK_BEL)
	lk_fd = lkopen (mailfile, O_RDWR);
#endif /* MH_LCK_BEL */
	flock (fd, LOCK_EX);
	malf = fdopen (fd, "r+");
    }

    if (fd < 0 || malf == NULL)
	panic ("can't rewrite %s", lfil);

    fstat (fd, &stbuf);

    if (stbuf.st_size != let[nlet].adr) {	/* new mail has arrived */
	fseek (malf, let[nlet].adr, L_SET);
	fseek (tmpf, let[nlet].adr, L_SET);

	while ((c = getc (malf)) != EOF)
	    putc (c, tmpf);

	let[++nlet].adr = stbuf.st_size;
	new = 1;
	fseek (malf, 0L, L_SET);
    }

    ftruncate (fd, 0L);

    for (i = 0; i < nlet; i++)
	if (let[i].change != 'd')
	    copylet (i, malf, ORDINARY);

    fclose (malf);		/* implict unlock */
#if defined (MH_LCK_BEL)
    (void) lkclose (lk_fd, mailfile);
#endif /* MH_LCK_BEL */

    if (new)
	printf ("New mail has arrived.\n");

    sigsetmask (oldmask);
}

/* copy mail (f1) to temp (f2) */

copymt (f1, f2)
    FILE *f1, *f2;
{
    long    nextadr;

    nlet = nextadr = 0;
    let[0].adr = 0;

    while (fgets (line, LSIZE, f1) != NULL) {
	if (isfrom (line))
	    let[nlet++].adr = nextadr;

	nextadr += strlen (line);
	fputs (line, f2);
    }

    let[nlet].adr = nextadr;	/* last plus 1 */
}

copylet	(n, f, type)
    int	    n;
    FILE    *f;
    int	    type;
{
    int	    ch;
    long    k;
    char    hostname[MAXHOSTNAMELEN];

    fseek (tmpf, let[n].adr, L_SET);
    k = let[n+1].adr - let[n].adr;

    while (k-- > 1 && (ch = getc (tmpf)) != '\n')
	if (type != ZAP)
	    putc (ch, f);

    switch (type) {
    case REMOTE:
	gethostname (hostname, sizeof (hostname));
	fprintf (f, " remote from %s\n", hostname);
	break;

    case FORWARD:
	fprintf (f, forwmsg);
	break;

    case ORDINARY:
	putc (ch, f);
	break;

    case ZAP:
	break;

    default:
	panic ("Bad letter type %d to copylet.", type);
    }

    while (k-- > 1) {
	ch = getc (tmpf);
	putc (ch, f);
    }

    if (type != ZAP || ch != '\n')
	putc (getc (tmpf), f);
}

isfrom (lp)
    register char  *lp;
{
    register char *p;

    for (p = from; *p; )
	if (*lp++ != *p++)
	    return (0);

    return (1);
}

bulkmail (argc, argv)
    int	    argc;
    char    **argv;
{
    char	    *truename;
    int		    first;
    register char   *cp;
    char	    *newargv[1000];
    register char   **ap;
    register char   **vp;
    int		    dflag;

    dflag = 0;
    delflg = 0;

    if (argc < 1) {
	fprintf (stderr, "puke\n");
	return;
    }

    for (vp = argv, ap = newargv + 1; (*ap = *vp++) != 0; ap++)
	if (ap[0][0] == '-' && ap[0][1] == 'd')
	    dflag++;

    if (!dflag) {
	/* give it to sendmail, rah rah! */

	unlink (lettmp);
	ap = newargv+1;

	if (rmail)
	    *ap-- = "-s";

	*ap = "-sendmail";
	setuid (getuid ());
	execv (_PATH_SENDMAIL, ap);
	perror (_PATH_SENDMAIL);
	exit (EX_UNAVAILABLE);
    }

    truename = 0;
    line[0] = '\0';

    /*
     *	When we fall out of this, argv[1] should be first name,
     *	argc should be number of names + 1.
     */

    while (argc > 1 && *argv[1] == '-') {
	cp = *++argv;
	argc--;

	switch (cp[1]) {
	case 'r':
	    if (argc <= 1)
		usage ();

	    truename = argv[1];
	    fgets (line, LSIZE, stdin);

	    if (strncmp ("From", line, 4) == 0)
		line[0] = '\0';

	    argv++;
	    argc--;

	    break;

	case 'h':
	    if (argc <= 1)
		usage ();

	    hseqno = atoi (argv[1]);
	    argv++;
	    argc--;

	    break;

	case 'd':
	    break;
		
	default:
	    usage ();
	}
    }

    if (argc <= 1)
	usage ();

    if (truename == 0)
	truename = my_name;

    time (&iop);
    fprintf (tmpf, "%s%s %s", from, truename, ctime (&iop));
    iop = ftell (tmpf);
    flgf = first = 1;

    for (;;) {
	if (first) {
	    first = 0;

	    if (*line == '\0' && fgets (line, LSIZE, stdin) == NULL)
		break;
	}
	else {
	    if (fgets (line, LSIZE, stdin) == NULL)
		break;
	}

	if (*line == '.' && line[1] == '\n' && isatty (fileno (stdin)))
	    break;

	if (isfrom (line))
	    putc ('>', tmpf);

	fputs (line, tmpf);
	flgf = 0;
    }

    putc ('\n', tmpf);
    nlet = 1;
    let[0].adr = 0;
    let[1].adr = ftell (tmpf);

    if (flgf)
	return;

    while (--argc > 0)
	if (!sendmail (0, *++argv, truename))
	    error++;

    if (error && safefile (dead)) {
	setuid (getuid ());
	malf = fopen (dead, "w");

	if (malf == NULL) {
	    printf ("mail: cannot open %s\n", dead);
	    fclose (tmpf);
	    return;
	}

	copylet (0, malf, ZAP);
	fclose (malf);
	printf ("Mail saved in %s\n", dead);
    }

    fclose (tmpf);
}

sendrmt (n, name)
    char    *name;
{
    FILE	    *rmf, *popen ();
    register char   *p;
    char	    rsys[64], cmd[64];
    register	    pid;
    int		    sts;

#ifdef notdef
    if (any ('^', name)) {
	while (p = index (name, '^'))
	    *p = '!';

	if (strncmp (name, "researc", 7)) {
	    strcpy (rsys, "research");

	    if (*name != '!')
		--name;

	    goto skip;
	}
    }
#endif

    for (p = rsys; *name != '!'; *p++ = *name++)
	if (*name == '\0')
	    return (0);	/* local address, no '!' */

    *p = '\0';

    if (name[1] == '\0') {
	printf ("null name\n");
	return (0);
    }

skip:
    if ((pid = fork ()) == -1) {
	fprintf (stderr, "mail: can't create proc for remote\n");
	return (0);
    }

    if (pid) {
	while (wait (&sts) != pid) {
	    if (wait (&sts) == -1)
		return (0);
	}

	return (!sts);
    }

    setuid (getuid ());

    if (any ('!', name+1))
	(void)sprintf (cmd, "uux - %s!rmail \\(%s\\)", rsys, name+1);
    else
	(void)sprintf (cmd, "uux - %s!rmail %s", rsys, name+1);

    if ((rmf = popen (cmd, "w")) == NULL)
	exit (1);

    copylet (n, rmf, REMOTE);
    exit (pclose (rmf) != 0);
}

usage ()
{
    fprintf (stderr, "Usage: mail [ -f ] people . . .\n");
    error = EX_USAGE;
    done ();
}

#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

notifybiff (msg)
    char    *msg;
{
    static struct sockaddr_in	addr;
    static int			f = -1;

    if (addr.sin_family == 0) {
	struct hostent	*hp = gethostbyname ("localhost");
	struct servent	*sp = getservbyname ("biff", "udp");

	if (hp && sp) {
	    addr.sin_family = hp->h_addrtype;
	    bcopy (hp->h_addr, &addr.sin_addr, hp->h_length);
	    addr.sin_port = sp->s_port;
	}
    }

    if (addr.sin_family) {
	if (f < 0)
	    f = socket (AF_INET, SOCK_DGRAM, 0);

	if (f >= 0)
	    sendto (f, msg, strlen (msg)+1, 0,
		    (struct sockaddr *) &addr, sizeof (addr));
    }
}

sendmail (n, name, fromaddr)
    int	    n;
    char    *name, *fromaddr;
{
    char	    file[256];
    int		    mask, fd;
    struct passwd   *pw;
#ifdef notdef
    struct stat	    statb;
#endif
    char	    buf[128];
#if defined (MH_LCK_BEL)
    int		    lk_fd;
    extern int	    lkopen (), lkclose ();
#endif /* MH_LCK_BEL */

    if (*name == '!')
	name++;

    if (any ('!', name))
	return (sendrmt (n, name));

    if ((pw = getpwnam (name)) == NULL) {
	printf ("mail: can't send to %s\n", name);
	return (0);
    }

    cat (file, maildir, name);

#ifdef notdef
    if (stat (file, &statb) >= 0 && (statb.st_mode & S_IFMT) == S_IFDIR) {
	strcat (file, "/");
	strcat (file, name);
    }
#endif

    if (! safefile (file))
	return (0);

    fd = open (file, O_WRONLY | O_CREAT, MAILMODE);

    if (fd >= 0) {
#if defined (MH_LCK_BEL)
	lk_fd = lkopen (mailfile, O_RDWR);
#endif /* MH_LCK_BEL */
	flock (fd, LOCK_EX);
	malf = fdopen (fd, "a");
    }

    if (fd < 0 || malf == NULL) {
	close (fd);
	printf ("mail: %s: cannot append\n", file);
	return (0);
    }

    fchown (fd, pw->pw_uid, pw->pw_gid);
    (void)sprintf (buf, "%s@%ld\n", name, ftell (malf));
    copylet (n, malf, ORDINARY);
    fclose (malf);

#if defined (MH_LCK_BEL)
    (void) lkclose (lk_fd, mailfile);
#endif /* MH_LCK_BEL */

    notifybiff (buf);

    return (1);
}

delex (i)
{
    if (i != SIGINT) {
	setsig (i, SIG_DFL);
	sigsetmask (sigblock (0L) &~ sigmask (i));
    }

    putc ('\n', stderr);

    if (delflg)
	longjmp (sjbuf, 1);

    if (error == 0)
	error = i;

    done ();
}

done ()
{

    unlink (lettmp);
    exit (error);
}

cat (to, from1, from2)
    char *to, *from1, *from2;
{
    register char *cp, *dp;

    cp = to;

    for (dp = from1; *cp = *dp++; cp++) ;

    for (dp = from2; *cp++ = *dp++; ) ;
}

/* copy p... into s, update p */
char *
getarg (s, p)
    register char *s, *p;
{
    while (*p == ' ' || *p == '\t')
	p++;

    if (*p == '\n' || *p == '\0')
	return (NULL);

    while (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\0')
	*s++ = *p++;

    *s = '\0';

    return (p);
}

safefile (f)
    char *f;
{
    struct stat statb;

    if (lstat (f, &statb) < 0)
	return (1);

    if (statb.st_nlink != 1 || (statb.st_mode & S_IFMT) == S_IFLNK) {
	fprintf (stderr,
		"mail: %s has more than one link or is a symbolic link\n",
		f);
	return (0);
    }

    return (1);
}

panic (msg, a1, a2, a3)
    char *msg;
{

    fprintf (stderr, "mail: ");
    fprintf (stderr, msg, a1, a2, a3);
    fprintf (stderr, "\n");
    done ();
}
