BSD 4_4_Lite1 release
[unix-history] / usr / src / libexec / mail.local / mail.local.c
index fdae281..0de8978 100644 (file)
-#include <ctype.h>
-#include <stdio.h>
-#include <pwd.h>
-#include <utmp.h>
-#include <signal.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <setjmp.h>
-#include <whoami.h>
-#include <sysexits.h>
+/*-
+ * Copyright (c) 1990, 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
 
 
-static char SccsId[] = "@(#)mail.local.c       4.6     %G%";
-
-#define DELIVERMAIL    "/etc/delivermail"
-
-
-/*copylet flags */
-       /*remote mail, add rmtmsg */
-#define REMOTE 1
-       /* zap header and trailing empty line */
-#define ZAP    3
-#define ORDINARY 2
-#define        FORWARD 4
-#define        LSIZE   256
-#define        MAXLET  300     /* maximum number of letters */
-#define        MAILMODE (~0644)                /* mode of created mail */
-# ifndef DELIVERMAIL
-#define        RMAIL   "/usr/net/bin/sendberkmail"
-#define LOCNAM1        "csvax"
-#define LOCNAM2        "ucbvax"
-#define LOCNAM3        "vax"
-#define LOCNAM4        "v"
-# endif
-
-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[] = "/tmp/maXXXXX";
-char   maildir[] = "/usr/spool/mail/";
-char   mailfile[] = "/usr/spool/mail/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
-char   dead[] = "dead.letter";
-char   *thissys = sysname;
-char   *netname = "vax";
-char   forwmsg[] = " forwarded\n";
-FILE   *tmpf;
-FILE   *malf;
-char   *my_name;
-char   *getlogin();
-struct passwd  *getpwuid();
-int    error;
-int    changed;
-int    forward;
-char   from[] = "From ";
-long   ftell();
-int    delete();
-char   *ctime();
-int    flgf;
-int    flgp;
-int    delflg = 1;
-int    hseqno;
-jmp_buf        sjbuf;
-int    rmail;
+#ifndef lint
+static char copyright[] =
+"@(#) Copyright (c) 1990, 1993, 1994\n\
+       The Regents of the University of California.  All rights reserved.\n";
+#endif /* not lint */
 
 
-main(argc, argv)
-char **argv;
-{
-       register i;
-       char sobuf[BUFSIZ];
-
-       setbuf(stdout, sobuf);
-       mktemp(lettmp);
-       unlink(lettmp);
-       my_name = getlogin();
-       if (my_name == NULL || strlen(my_name) == 0) {
-               struct passwd *pwent;
-               pwent = getpwuid(getuid());
-               if (pwent==NULL)
-                       my_name = "???";
-               else
-                       my_name = pwent->pw_name;
-       }
-       if(setjmp(sjbuf)) done();
-       for (i=0; i<20; i++)
-               setsig(i, delete);
-       tmpf = fopen(lettmp, "w");
-       if (tmpf == NULL) {
-               fprintf(stderr, "mail: cannot open %s for writing\n", lettmp);
-               done();
-       }
-       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();
-}
+#ifndef lint
+static char sccsid[] = "@(#)mail.local.c       8.6 (Berkeley) 4/8/94";
+#endif /* not lint */
 
 
-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;
-{
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
 
 
-       while (*str)
-               if (c == *str++)
-                       return(1);
-       return(0);
-}
+#include <netinet/in.h>
 
 
-printmail(argc, argv)
-char **argv;
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <syslog.h>
+#include <time.h>
+#include <unistd.h>
+
+#if __STDC__
+#include <stdarg.h>
+#else
+#include <varargs.h>
+#endif
+
+#include "pathnames.h"
+
+int eval = EX_OK;                      /* sysexits.h error value. */
+
+void           deliver __P((int, char *));
+void           e_to_sys __P((int));
+__dead void    err __P((const char *, ...));
+void           notifybiff __P((char *));
+int            store __P((char *));
+void           usage __P((void));
+void           vwarn __P((const char *, _BSD_VA_LIST_));
+void           warn __P((const char *, ...));
+
+int
+main(argc, argv)
+       int argc;
+       char *argv[];
 {
 {
-       int flg, i, j, print;
-       char *p, *getarg();
-       struct stat statb;
-
-       setuid(getuid());
-       cat(mailfile, maildir, my_name);
-       if (stat(mailfile, &statb) >= 0
-           && (statb.st_mode & S_IFMT) == S_IFDIR) {
-               strcat(mailfile, "/");
-               strcat(mailfile, my_name);
-       }
-       for (; argc>1; argv++, argc--) {
-               if (argv[1][0]=='-') {
-                       if (argv[1][1]=='q')
-                               delflg = 0;
-                       else if (argv[1][1]=='p') {
-                               flgp++;
-                               delflg = 0;
-                       } else if (argv[1][1]=='f') {
-                               if (argc>=3) {
-                                       strcpy(mailfile, argv[2]);
-                                       argv++;
-                                       argc--;
-                               }
-                       } else if (argv[1][1]=='r') {
-                               forward = 1;
-                       } else if (argv[1][1]=='h') {
-                               forward = 1;
-                       } else {
-                               fprintf(stderr, "mail: unknown option %c\n", argv[1][1]);
-                               done();
-                       }
-               } else
-                       break;
-       }
-       malf = fopen(mailfile, "r");
-       if (malf == NULL) {
-               fprintf(stdout, "No mail.\n");
-               return;
-       }
-       lock(mailfile);
-       copymt(malf, tmpf);
-       fclose(malf);
-       fclose(tmpf);
-       unlock();
-       tmpf = fopen(lettmp, "r");
-
-       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);
-               fprintf(stdout, "? ");
-               fflush(stdout);
-               if (fgets(resp, LSIZE, stdin) == NULL)
-                       break;
-               switch (resp[0]) {
+       struct passwd *pw;
+       int ch, fd;
+       uid_t uid;
+       char *from;
 
 
-               default:
-                       fprintf(stderr, "usage\n");
-               case '?':
-                       print = 0;
-                       fprintf(stderr, "q\tquit\n");
-                       fprintf(stderr, "x\texit without changing mail\n");
-                       fprintf(stderr, "p\tprint\n");
-                       fprintf(stderr, "s[file]\tsave (default mbox)\n");
-                       fprintf(stderr, "w[file]\tsame without header\n");
-                       fprintf(stderr, "-\tprint previous\n");
-                       fprintf(stderr, "d\tdelete\n");
-                       fprintf(stderr, "+\tnext (no delete)\n");
-                       fprintf(stderr, "m user\tmail to user\n");
-                       fprintf(stderr, "! cmd\texecute cmd\n");
-                       break;
+       openlog("mail.local", 0, LOG_MAIL);
 
 
-               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) {
-                                       fprintf(stdout, "mail: cannot append to %s\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++;
-                       }
+       from = NULL;
+       while ((ch = getopt(argc, argv, "df:r:")) != EOF)
+               switch(ch) {
+               case 'd':               /* Backward compatible. */
                        break;
                        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 (!sendrmt(j, lfil, "/bin/mail"))     /* couldn't send it */
-                                       flg++;
-                       if (flg)
-                               print = 0;
-                       else {
-                               let[j].change = 'd';
-                               changed++;
-                               i++;
+               case 'f':
+               case 'r':               /* Backward compatible. */
+                       if (from != NULL) {
+                               warn("multiple -f options");
+                               usage();
                        }
                        }
+                       from = optarg;
                        break;
                        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;
+               case '?':
+               default:
+                       usage();
                }
                }
-       }
-   donep:
-       if (changed)
-               copyback();
-}
+       argc -= optind;
+       argv += optind;
 
 
-copyback()     /* copy temp or whatever back to /usr/spool/mail */
-{
-       register i, n, c;
-       int new = 0;
-       struct stat stbuf;
-
-       signal(SIGINT, SIG_IGN);
-       signal(SIGHUP, SIG_IGN);
-       signal(SIGQUIT, SIG_IGN);
-       lock(mailfile);
-       stat(mailfile, &stbuf);
-       if (stbuf.st_size != let[nlet].adr) {   /* new mail has arrived */
-               malf = fopen(mailfile, "r");
-               if (malf == NULL) {
-                       fprintf(stdout, "mail: can't re-read %s\n", mailfile);
-                       done();
-               }
-               fseek(malf, let[nlet].adr, 0);
-               fclose(tmpf);
-               tmpf = fopen(lettmp, "a");
-               fseek(tmpf, let[nlet].adr, 0);
-               while ((c = fgetc(malf)) != EOF)
-                       fputc(c, tmpf);
-               fclose(malf);
-               fclose(tmpf);
-               tmpf = fopen(lettmp, "r");
-               let[++nlet].adr = stbuf.st_size;
-               new = 1;
-       }
-       malf = fopen(mailfile, "w");
-       if (malf == NULL) {
-               fprintf(stderr, "mail: can't rewrite %s\n", lfil);
-               done();
-       }
-       n = 0;
-       for (i = 0; i < nlet; i++)
-               if (let[i].change != 'd') {
-                       copylet(i, malf, ORDINARY);
-                       n++;
-               }
-       fclose(malf);
-       if (new)
-               fprintf(stdout, "new mail arrived\n");
-       unlock();
-}
+       if (!*argv)
+               usage();
 
 
-copymt(f1, f2) /* copy mail (f1) to temp (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 */
-}
+       /*
+        * If from not specified, use the name from getlogin() if the
+        * uid matches, otherwise, use the name from the password file
+        * corresponding to the uid.
+        */
+       uid = getuid();
+       if (!from && (!(from = getlogin()) ||
+           !(pw = getpwnam(from)) || pw->pw_uid != uid))
+               from = (pw = getpwuid(uid)) ? pw->pw_name : "???";
 
 
-copylet(n, f, type) FILE *f;
-{      int ch, k;
-       fseek(tmpf, let[n].adr, 0);
-       k = let[n+1].adr - let[n].adr;
-       while(k-- > 1 && (ch=fgetc(tmpf))!='\n')
-               if(type!=ZAP) fputc(ch,f);
-       if(type==REMOTE)
-               fprintf(f, " remote from %s\n", thissys);
-       else if (type==FORWARD)
-               fprintf(f, forwmsg);
-       else if(type==ORDINARY)
-               fputc(ch,f);
-       while(k-->1)
-               fputc(ch=fgetc(tmpf), f);
-       if(type!=ZAP || ch!= '\n')
-               fputc(fgetc(tmpf), f);
+       /*
+        * There is no way to distinguish the error status of one delivery
+        * from the rest of the deliveries.  So, if we failed hard on one
+        * or more deliveries, but had no failures on any of the others, we
+        * return a hard failure.  If we failed temporarily on one or more
+        * deliveries, we return a temporary failure regardless of the other
+        * failures.  This results in the delivery being reattempted later
+        * at the expense of repeated failures and multiple deliveries.
+        */
+       for (fd = store(from); *argv; ++argv)
+               deliver(fd, *argv);
+       exit(eval);
 }
 
 }
 
-isfrom(lp)
-register char *lp;
+int
+store(from)
+       char *from;
 {
 {
-       register char *p;
+       FILE *fp;
+       time_t tval;
+       int fd, eline;
+       char *tn, line[2048];
+
+       tn = strdup(_PATH_LOCTMP);
+       if ((fd = mkstemp(tn)) == -1 || (fp = fdopen(fd, "w+")) == NULL) {
+               e_to_sys(errno);
+               err("unable to open temporary file");
+       }
+       (void)unlink(tn);
+       free(tn);
 
 
-       for (p = from; *p; )
-               if (*lp++ != *p++)
-                       return(0);
-       return(1);
-}
+       (void)time(&tval);
+       (void)fprintf(fp, "From %s %s", from, ctime(&tval));
 
 
-bulkmail(argc, argv)
-char **argv;
-{
-       char truename[100];
-       int first;
-       register char *cp;
-       int gaver = 0;
-# ifdef DELIVERMAIL
-       char *newargv[1000];
-       register char **ap;
-       register char **vp;
-       int dflag;
-
-       dflag = 0;
-       if (argc < 1)
-               fprintf(stderr, "puke\n");
-       for (vp = argv, ap = newargv + 1; (*ap = *vp++) != 0; ap++)
-       {
-               if (ap[0][0] == '-' && ap[0][1] == 'd')
-                       dflag++;
+       line[0] = '\0';
+       for (eline = 1; fgets(line, sizeof(line), stdin);) {
+               if (line[0] == '\n')
+                       eline = 1;
+               else {
+                       if (eline && line[0] == 'F' &&
+                           !memcmp(line, "From ", 5))
+                               (void)putc('>', fp);
+                       eline = 0;
+               }
+               (void)fprintf(fp, "%s", line);
+               if (ferror(fp)) {
+                       e_to_sys(errno);
+                       err("temporary file write error");
+               }
        }
        }
-       if (!dflag)
-       {
-               /* give it to delivermail, rah rah! */
-               unlink(lettmp);
-               ap = newargv+1;
-               if (rmail)
-                       *ap-- = "-s";
-               *ap = "-delivermail";
-               setuid(getuid());
-               execv(DELIVERMAIL, ap);
-               perror(DELIVERMAIL);
-               exit(EX_UNAVAILABLE);
+
+       /* If message not newline terminated, need an extra. */
+       if (!strchr(line, '\n'))
+               (void)putc('\n', fp);
+       /* Output a newline; note, empty messages are allowed. */
+       (void)putc('\n', fp);
+
+       if (fflush(fp) == EOF || ferror(fp)) {
+               e_to_sys(errno);
+               err("temporary file write error");
        }
        }
-# endif DELIVERMAIL
+       return (fd);
+}
 
 
-       truename[0] = 0;
-       line[0] = '\0';
+void
+deliver(fd, name)
+       int fd;
+       char *name;
+{
+       struct stat fsb, sb;
+       struct passwd *pw;
+       int mbfd, nr, nw, off;
+       char biffmsg[100], buf[8*1024], path[MAXPATHLEN];
+       off_t curoff;
 
        /*
 
        /*
-        * When we fall out of this, argv[1] should be first name,
-        * argc should be number of names + 1.
+        * Disallow delivery to unknown names -- special mailboxes can be
+        * handled in the sendmail aliases file.
         */
         */
+       if (!(pw = getpwnam(name))) {
+               if (eval != EX_TEMPFAIL)
+                       eval = EX_UNAVAILABLE;
+               warn("unknown name: %s", name);
+               return;
+       }
 
 
-       while (argc > 1 && *argv[1] == '-') {
-               cp = *++argv;
-               argc--;
-               switch (cp[1]) {
-               case 'r':
-                       if (argc <= 0) {
-                               usage();
-                               done();
-                       }
-                       gaver++;
-                       strcpy(truename, argv[1]);
-                       fgets(line, LSIZE, stdin);
-                       if (strcmpn("From", line, 4) == 0)
-                               line[0] = '\0';
-                       argv++;
-                       argc--;
-                       break;
+       (void)snprintf(path, sizeof(path), "%s/%s", _PATH_MAILDIR, name);
 
 
-               case 'h':
-                       if (argc <= 0) {
-                               usage();
-                               done();
-                       }
-                       hseqno = atoi(argv[1]);
-                       argv++;
-                       argc--;
-                       break;
-
-# ifdef DELIVERMAIL
-               case 'd':
-                       break;
-# endif DELIVERMAIL
-               
-               default:
-                       usage();
-                       done();
-               }
-       }
-       if (argc <= 1) {
-               usage();
-               done();
-       }
-       if (gaver == 0)
-               strcpy(truename, my_name);
        /*
        /*
-       if (argc > 4 && strcmp(argv[1], "-r") == 0) {
-               strcpy(truename, argv[2]);
-               argc -= 2;
-               argv += 2;
-               fgets(line, LSIZE, stdin);
-               if (strcmpn("From", line, 4) == 0)
-                       line[0] = '\0';
-       } else
-               strcpy(truename, my_name);
-       */
-       time(&iop);
-       fprintf(tmpf, "%s%s %s", from, truename, ctime(&iop));
-       iop = ftell(tmpf);
-       flgf = 1;
-       for (first = 1;; first = 0) {
-               if (first && line[0] == '\0' && fgets(line, LSIZE, stdin) == NULL)
-                       break;
-               if (!first && fgets(line, LSIZE, stdin) == NULL)
-                       break;
-               if (line[0] == '.' && line[1] == '\n' && isatty(fileno(stdin)))
-                       break;
-               if (isfrom(line))
-                       fputs(">", tmpf);
-               fputs(line, tmpf);
-               flgf = 0;
-       }
-       fputs("\n", tmpf);
-       nlet = 1;
-       let[0].adr = 0;
-       let[1].adr = ftell(tmpf);
-       fclose(tmpf);
-       if (flgf)
-               return;
-       tmpf = fopen(lettmp, "r");
-       if (tmpf == NULL) {
-               fprintf(stderr, "mail: cannot reopen %s for reading\n", lettmp);
+        * If the mailbox is linked or a symlink, fail.  There's an obvious
+        * race here, that the file was replaced with a symbolic link after
+        * the lstat returned, but before the open.  We attempt to detect
+        * this by comparing the original stat information and information
+        * returned by an fstat of the file descriptor returned by the open.
+        *
+        * NB: this is a symptom of a larger problem, that the mail spooling
+        * directory is writeable by the wrong users.  If that directory is
+        * writeable, system security is compromised for other reasons, and
+        * it cannot be fixed here.
+        *
+        * If we created the mailbox, set the owner/group.  If that fails,
+        * just return.  Another process may have already opened it, so we
+        * can't unlink it.  Historically, binmail set the owner/group at
+        * each mail delivery.  We no longer do this, assuming that if the
+        * ownership or permissions were changed there was a reason.
+        *
+        * XXX
+        * open(2) should support flock'ing the file.
+        */
+tryagain:
+       if (lstat(path, &sb)) {
+               mbfd = open(path,
+                   O_APPEND|O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR);
+               if (mbfd == -1) {
+                       if (errno == EEXIST)
+                               goto tryagain;
+               } else if (fchown(mbfd, pw->pw_uid, pw->pw_gid)) {
+                       e_to_sys(errno);
+                       warn("chown %u.%u: %s", pw->pw_uid, pw->pw_gid, name);
+                       return;
+               }
+       } else if (sb.st_nlink != 1 || S_ISLNK(sb.st_mode)) {
+               e_to_sys(errno);
+               warn("%s: linked file", path);
                return;
                return;
-       }
-       while (--argc > 0) {
-               if (!sendmail(0, *++argv, truename))
-                       error++;
-       }
-       if (error) {
-               setuid(getuid());
-               malf = fopen(dead, "w");
-               if (malf == NULL) {
-                       fprintf(stdout, "mail: cannot open %s\n", dead);
-                       fclose(tmpf);
+       } else {
+               mbfd = open(path, O_APPEND|O_WRONLY, 0);
+               if (mbfd != -1 &&
+                   (fstat(mbfd, &fsb) || fsb.st_nlink != 1 ||
+                   S_ISLNK(fsb.st_mode) || sb.st_dev != fsb.st_dev ||
+                   sb.st_ino != fsb.st_ino)) {
+                       warn("%s: file changed after open", path);
+                       (void)close(mbfd);
                        return;
                }
                        return;
                }
-               copylet(0, malf, ZAP);
-               fclose(malf);
-               fprintf(stdout, "Mail saved in %s\n", dead);
        }
        }
-       fclose(tmpf);
-}
 
 
-sendrmt(n, name, rcmd)
-char *name;
-char *rcmd;
-{
-       FILE *rmf, *popen();
-       register char *p;
-       char rsys[64], cmd[64];
-       register local, pid;
-       int sts;
-
-       local = 0;
-       if (index(name, '^')) {
-               while (p = index(name, '^'))
-                       *p = '!';
-               if (strncmp(name, "researc", 7)) {
-                       strcpy(rsys, "research");
-                       if (*name != '!')
-                               --name;
-                       goto skip;
-               }
-       }
-       if (*name=='!')
-               name++;
-       for(p=rsys; *name!='!'; *p++ = *name++)
-               if (*name=='\0') {
-                       local++;
-                       break;
-               }
-       *p = '\0';
-       if ((!local && *name=='\0') || (local && *rsys=='\0')) {
-               fprintf(stdout, "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 (local)
-               sprintf(cmd, "%s %s", rcmd, rsys);
-       else {
-               if (index(name+1, '!'))
-                       sprintf(cmd, "uux - %s!rmail \\(%s\\)", rsys, name+1);
-               else
-                       sprintf(cmd, "uux - %s!rmail %s", rsys, name+1);
+       if (mbfd == -1) {
+               e_to_sys(errno);
+               warn("%s: %s", path, strerror(errno));
+               return;
        }
        }
-       if ((rmf=popen(cmd, "w")) == NULL)
-               exit(1);
-       copylet(n, rmf, local ? !strcmp(rcmd, "/bin/mail") ? FORWARD : ORDINARY : REMOTE);
-       pclose(rmf);
-       exit(0);
-}
 
 
-# ifndef DELIVERMAIL
-/*
- * Send mail on the Berkeley network.
- * Sorry Bill, sendrmt() is so awful we just gave up.
- */
-
-sendberkmail(n, name, fromaddr)
-       char name[];
-       char fromaddr[];
-{
-       char cmd[200];
-       register FILE *cmdf;
-
-       sprintf(cmd, "%s -h %d -f %s -t %s", RMAIL, hseqno, fromaddr, name);
-       if ((cmdf = popen(cmd, "w")) == NULL) {
-               perror(RMAIL);
-               return(0);
+       /* Wait until we can get a lock on the file. */
+       if (flock(mbfd, LOCK_EX)) {
+               e_to_sys(errno);
+               warn("%s: %s", path, strerror(errno));
+               goto err1;
        }
        }
-       copylet(n, cmdf, ORDINARY);
-       pclose(cmdf);
-       return(9);
-}
-# endif
 
 
-usage()
-{
-
-       fprintf(stderr, "Usage: mail [ -f ] people . . .\n");
-}
+       /* Get the starting offset of the new message for biff. */
+       curoff = lseek(mbfd, (off_t)0, SEEK_END);
+       (void)snprintf(biffmsg, sizeof(biffmsg), "%s@%qd\n", name, curoff);
 
 
-#include <sys/socket.h>
-#include <net/in.h>
-#include <wellknown.h>
-struct sockaddr_in biffaddr = { AF_INET, IPPORT_BIFFUDP };
-char *localhost = "localhost";
-
-sendmail(n, name, fromaddr)
-int n;
-char *name;
-char *fromaddr;
-{
-       char file[100];
-       register char *p;
-       register mask;
-       struct passwd *pw, *getpwnam();
-       struct stat statb;
-       char buf[128];
-       int f;
-
-# ifndef DELIVERMAIL
-       stripfx(LOCNAM1, &name);
-       stripfx(LOCNAM2, &name);
-       stripfx(LOCNAM3, &name);
-       stripfx(LOCNAM4, &name);
-       if(*name == ':')name++;         /* skip colon in to-name */
-       for(p=name; *p!=':' && *p!='!' && *p!='^' &&*p!='\0'; p++);
-       /* if(*p == ':') return(sendrmt(n, name, RMAIL)); */
-       if (*p == ':')
-               return(sendberkmail(n, name, fromaddr));
-       else if (*p=='\0' && strcmp(name, "msgs") == 0)
-               return(sendrmt(n, "-s", "/usr/ucb/msgs"));
-# endif
-       for(p=name; *p!='!'&&*p!='^' &&*p!='\0'; p++)
-               ;
-       if (*p == '!'|| *p=='^')
-               return(sendrmt(n, name, 0));
-       if ((pw = getpwnam(name)) == NULL) {
-               fprintf(stdout, "mail: can't send to %s\n", name);
-               return(0);
-       }
-       cat(file, maildir, name);
-       if (stat(file, &statb) >= 0 && (statb.st_mode & S_IFMT) == S_IFDIR) {
-               strcat(file, "/");
-               strcat(file, name);
+       /* Copy the message into the file. */
+       if (lseek(fd, (off_t)0, SEEK_SET) == (off_t)-1) {
+               e_to_sys(errno);
+               warn("temporary file: %s", strerror(errno));
+               goto err1;
        }
        }
-       mask = umask(MAILMODE);
-       if (stat(file, &statb) >= 0 && statb.st_nlink != 1) {
-               fprintf(stdout, "mail: %s's mail file has more than one link\n", name);
-               return(0);
-       }
-       malf = fopen(file, "a");
-       umask(mask);
-       if (malf == NULL) {
-               fprintf(stdout, "mail: cannot append to %s\n", file);
-               return(0);
+       while ((nr = read(fd, buf, sizeof(buf))) > 0)
+               for (off = 0; off < nr; nr -= nw, off += nw)
+                       if ((nw = write(mbfd, buf + off, nr)) < 0) {
+                               e_to_sys(errno);
+                               warn("%s: %s", path, strerror(errno));
+                               goto err2;;
+                       }
+       if (nr < 0) {
+               e_to_sys(errno);
+               warn("temporary file: %s", strerror(errno));
+               goto err2;;
        }
        }
-       lock(file);
-       chown(file, pw->pw_uid, pw->pw_gid);
-       {
-               f = socket(SOCK_DGRAM, 0, 0, 0);
-               sprintf(buf, "%s@%d\n", name, ftell(malf)); 
+
+       /* Flush to disk, don't wait for update. */
+       if (fsync(mbfd)) {
+               e_to_sys(errno);
+               warn("%s: %s", path, strerror(errno));
+err2:          (void)ftruncate(mbfd, curoff);
+err1:          (void)close(mbfd);
+               return;
        }
        }
-       copylet(n, malf, ORDINARY);
-       fclose(malf);
-       if (f >= 0) {
-               biffaddr.sin_addr.s_addr = rhost(&localhost);
-               send(f, &biffaddr, buf, strlen(buf)+1);
-               close(f);
+               
+       /* Close and check -- NFS doesn't write until the close. */
+       if (close(mbfd)) {
+               e_to_sys(errno);
+               warn("%s: %s", path, strerror(errno));
+               return;
        }
        }
-       unlock();
-       return(1);
-}
 
 
-delete(i)
-{
-       setsig(i, delete);
-       fprintf(stderr, "\n");
-       if(delflg)
-               longjmp(sjbuf, 1);
-       done();
+       notifybiff(biffmsg);
 }
 
 }
 
-/*
- * Lock the specified mail file by setting the file mailfile.lock.
- * We must, of course, be careful to unlink the lock file by a call
- * to unlock before we stop.  The algorithm used here is to see if
- * the lock exists, and if it does, to check its modify time.  If it
- * is older than 30 seconds, we assume error and set our own file.
- * Otherwise, we wait for 5 seconds and try again.
- */
-
-char   *maillock       = ".lock";              /* Lock suffix for mailname */
-char   *lockname       = "/usr/spool/mail/tmXXXXXX";
-char   locktmp[30];                            /* Usable lock temporary */
-char   curlock[50];                            /* Last used name of lock */
-int    locked;                                 /* To note that we locked it */
-
-lock(file)
-char *file;
+void
+notifybiff(msg)
+       char *msg;
 {
 {
-       register int f;
-       struct stat sbuf;
-       long curtime;
-       int statfailed;
-
-       if (locked || flgf)
-               return(0);
-       strcpy(curlock, file);
-       strcat(curlock, maillock);
-       strcpy(locktmp, lockname);
-       mktemp(locktmp);
-       unlink(locktmp);
-       statfailed = 0;
-       for (;;) {
-               f = lock1(locktmp, curlock);
-               if (f == 0) {
-                       locked = 1;
-                       return(0);
-               }
-               if (stat(curlock, &sbuf) < 0) {
-                       if (statfailed++ > 5)
-                               return(-1);
-                       sleep(5);
-                       continue;
-               }
-               statfailed = 0;
-               time(&curtime);
-               if (curtime < sbuf.st_ctime + 30) {
-                       sleep(5);
-                       continue;
+       static struct sockaddr_in addr;
+       static int f = -1;
+       struct hostent *hp;
+       struct servent *sp;
+       int len;
+
+       if (!addr.sin_family) {
+               /* Be silent if biff service not available. */
+               if (!(sp = getservbyname("biff", "udp")))
+                       return;
+               if (!(hp = gethostbyname("localhost"))) {
+                       warn("localhost: %s", strerror(errno));
+                       return;
                }
                }
-               unlink(curlock);
+               addr.sin_family = hp->h_addrtype;
+               memmove(&addr.sin_addr, hp->h_addr, hp->h_length);
+               addr.sin_port = sp->s_port;
+       }
+       if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
+               warn("socket: %s", strerror(errno));
+               return;
        }
        }
+       len = strlen(msg) + 1;
+       if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof(addr))
+           != len)
+               warn("sendto biff: %s", strerror(errno));
 }
 
 }
 
-/*
- * Remove the mail lock, and note that we no longer
- * have it locked.
- */
-
-unlock()
+void
+usage()
 {
 {
-
-       unlink(curlock);
-       locked = 0;
+       eval = EX_USAGE;
+       err("usage: mail.local [-f from] user ...");
 }
 
 }
 
-/*
- * Attempt to set the lock by creating the temporary file,
- * then doing a link/unlink.  If it fails, return -1 else 0
- */
-
-lock1(tempfile, name)
-       char tempfile[], name[];
+#if __STDC__
+void
+err(const char *fmt, ...)
+#else
+void
+err(fmt, va_alist)
+       const char *fmt;
+       va_dcl
+#endif
 {
 {
-       register int fd;
-
-       fd = creat(tempfile, 0);
-       if (fd < 0)
-               return(-1);
-       close(fd);
-       if (link(tempfile, name) < 0) {
-               unlink(tempfile);
-               return(-1);
-       }
-       unlink(tempfile);
-       return(0);
-}
+       va_list ap;
 
 
-done()
-{
-       if(locked)
-               unlock();
-       unlink(lettmp);
-       unlink(locktmp);
-       exit(error);
+#if __STDC__
+       va_start(ap, fmt);
+#else
+       va_start(ap);
+#endif
+       vwarn(fmt, ap);
+       va_end(ap);
+
+       exit(eval);
 }
 
 }
 
-cat(to, from1, from2)
-char *to, *from1, *from2;
+void
+#if __STDC__
+warn(const char *fmt, ...)
+#else
+warn(fmt, va_alist)
+       const char *fmt;
+       va_dcl
+#endif
 {
 {
-       int i, j;
-
-       j = 0;
-       for (i=0; from1[i]; i++)
-               to[j++] = from1[i];
-       for (i=0; from2[i]; i++)
-               to[j++] = from2[i];
-       to[j] = 0;
+       va_list ap;
+
+#if __STDC__
+       va_start(ap, fmt);
+#else
+       va_start(ap);
+#endif
+       vwarn(fmt, ap);
+       va_end(ap);
 }
 
 }
 
-char *getarg(s, p)     /* copy p... into s, update p */
-register char *s, *p;
+void
+vwarn(fmt, ap)
+       const char *fmt;
+       _BSD_VA_LIST_ ap;
 {
 {
-       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);
+       /*
+        * Log the message to stderr.
+        *
+        * Don't use LOG_PERROR as an openlog() flag to do this,
+        * it's not portable enough.
+        */
+       if (eval != EX_USAGE)
+               (void)fprintf(stderr, "mail.local: ");
+       (void)vfprintf(stderr, fmt, ap);
+       (void)fprintf(stderr, "\n");
+
+       /* Log the message to syslog. */
+       vsyslog(LOG_ERR, fmt, ap);
 }
 }
-# ifndef DELIVERMAIL
+
 /*
 /*
-       stripfx(prefix string, pointer to string)
-
-       takes a ptr to string and compares it to prefix string.
-       may be called multiple times
-*/
-stripfx(pfx, name)
-       char *pfx;
-       char **name;
+ * e_to_sys --
+ *     Guess which errno's are temporary.  Gag me.
+ */
+void
+e_to_sys(num)
+       int num;
 {
 {
-       register char *cp = *name;
-
-       while (*pfx && (*cp == *pfx || *cp == toupper(*pfx)))
-               cp++, pfx++;
-       if (*cp != ':' || *pfx != 0)
+       /* Temporary failures override hard errors. */
+       if (eval == EX_TEMPFAIL)
                return;
                return;
-       *name = cp;
+
+       switch(num) {           /* Hopefully temporary errors. */
+#ifdef EAGAIN
+       case EAGAIN:            /* Resource temporarily unavailable */
+#endif
+#ifdef EDQUOT
+       case EDQUOT:            /* Disc quota exceeded */
+#endif
+#ifdef EBUSY
+       case EBUSY:             /* Device busy */
+#endif
+#ifdef EPROCLIM
+       case EPROCLIM:          /* Too many processes */
+#endif
+#ifdef EUSERS
+       case EUSERS:            /* Too many users */
+#endif
+#ifdef ECONNABORTED
+       case ECONNABORTED:      /* Software caused connection abort */
+#endif
+#ifdef ECONNREFUSED
+       case ECONNREFUSED:      /* Connection refused */
+#endif
+#ifdef ECONNRESET
+       case ECONNRESET:        /* Connection reset by peer */
+#endif
+#ifdef EDEADLK
+       case EDEADLK:           /* Resource deadlock avoided */
+#endif
+#ifdef EFBIG
+       case EFBIG:             /* File too large */
+#endif
+#ifdef EHOSTDOWN
+       case EHOSTDOWN:         /* Host is down */
+#endif
+#ifdef EHOSTUNREACH
+       case EHOSTUNREACH:      /* No route to host */
+#endif
+#ifdef EMFILE
+       case EMFILE:            /* Too many open files */
+#endif
+#ifdef ENETDOWN
+       case ENETDOWN:          /* Network is down */
+#endif
+#ifdef ENETRESET
+       case ENETRESET:         /* Network dropped connection on reset */
+#endif
+#ifdef ENETUNREACH
+       case ENETUNREACH:       /* Network is unreachable */
+#endif
+#ifdef ENFILE
+       case ENFILE:            /* Too many open files in system */
+#endif
+#ifdef ENOBUFS
+       case ENOBUFS:           /* No buffer space available */
+#endif
+#ifdef ENOMEM
+       case ENOMEM:            /* Cannot allocate memory */
+#endif
+#ifdef ENOSPC
+       case ENOSPC:            /* No space left on device */
+#endif
+#ifdef EROFS
+       case EROFS:             /* Read-only file system */
+#endif
+#ifdef ESTALE
+       case ESTALE:            /* Stale NFS file handle */
+#endif
+#ifdef ETIMEDOUT
+       case ETIMEDOUT:         /* Connection timed out */
+#endif
+#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
+       case EWOULDBLOCK:       /* Operation would block. */
+#endif
+               eval = EX_TEMPFAIL;
+               break;
+       default:
+               eval = EX_UNAVAILABLE;
+               break;
+       }
 }
 }
-# endif