root/usr/src/cmd/sendmail/util/mail.local.c
/*
 * Copyright (c) 1998 Sendmail, Inc.  All rights reserved.
 * Copyright (c) 1990, 1993, 1994
 *      The Regents of the University of California.  All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level
 * of the sendmail distribution.
 */

/*
 * Copyright 1994-2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#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 */

#ifndef lint
static char sccsid[] = "@(#)mail.local.c        8.83 (Berkeley) 12/17/98";
static char sccsi2[] = "%W% (Sun) %G%";
#endif /* not lint */

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

#include <netinet/in.h>

#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <ctype.h>
#include <string.h>
#include <sysexits.h>
#include <time.h>
#include <unistd.h>
#include <maillock.h>
#include <grp.h>

#ifdef __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif

#include <syslog.h>

#include <sysexits.h>
#include <ctype.h>

#include <sm/conf.h>
#include <sendmail/pathnames.h>

/*
**  If you don't have flock, you could try using lockf instead.
*/

#ifdef LDA_USE_LOCKF
# define flock(a, b)    lockf(a, b, 0)
# ifdef LOCK_EX
#  undef LOCK_EX
# endif /* LOCK_EX */
# define LOCK_EX        F_LOCK
#endif /* LDA_USE_LOCKF */

#ifndef LOCK_EX
# include <sys/file.h>
#endif /* ! LOCK_EX */

#ifndef MAILER_DAEMON
# define MAILER_DAEMON  "MAILER-DAEMON"
#endif

typedef int bool;

#define FALSE   0
#define TRUE    1

bool    EightBitMime = TRUE;            /* advertise 8BITMIME in LMTP */
static int eval = EX_OK;                        /* sysexits.h error value. */
static int lmtpmode = 0;
bool    bouncequota = FALSE;            /* permanent error when over quota */

#define _PATH_MAILDIR   "/var/mail"
#define _PATH_LOCTMP    "/tmp/local.XXXXXX"
#define _PATH_LOCHTMP   "/tmp/lochd.XXXXXX"
#define FALSE 0
#define TRUE  1
#define MAXLINE 2048

static void     deliver(int, int, char *, bool);
static void     e_to_sys(int);
static void     err(const char *fmt, ...);
static void     notifybiff(char *);
static void     store(char *, int);
static void     usage(void);
static void     vwarn();
static void     warn(const char *fmt, ...);
static void     mailerr(const char *, const char *, ...);
static void     sigterm_handler();

static char     unix_from_line[MAXLINE];
static int      ulen;
static int      content_length;
static int      bfd, hfd; /* temp file */
static uid_t    src_uid, targ_uid, saved_uid;
static int      sigterm_caught;

int
main(argc, argv)
        int argc;
        char *argv[];
{
        struct passwd *pw;
        int ch;
        uid_t uid;
        char *from;
        struct  group *grpptr;
        void dolmtp();

        openlog("mail.local", 0, LOG_MAIL);

        from = NULL;
        pw = NULL;
        sigterm_caught = FALSE;

        (void) sigset(SIGTERM, sigterm_handler);

        while ((ch = getopt(argc, argv, "7bdf:r:l")) != EOF)
                switch (ch) {
                case '7':               /* Do not advertise 8BITMIME */
                        EightBitMime = FALSE;
                        break;

                case 'b':               /* bounce mail when over quota. */
                        bouncequota = TRUE;
                        break;

                case 'd':               /* Backward compatible. */
                        break;
                case 'f':
                case 'r':               /* Backward compatible. */
                        if (from != NULL) {
                                warn("multiple -f options");
                                usage();
                        }
                        from = optarg;
                        break;
                case 'l':
                        lmtpmode++;
                        break;
                case '?':
                default:
                        usage();
                }
        argc -= optind;
        argv += optind;

        notifybiff(NULL); /* initialize biff structures */

        /*
         * We expect sendmail will invoke us with saved id 0
         * We then do setgid and setuid defore delivery
         * setgid to mail group
         */
        if ((grpptr = getgrnam("mail")) != NULL)
                (void) setgid(grpptr->gr_gid);
        saved_uid = geteuid();

        if (lmtpmode) {
                if (saved_uid != 0) {
                        warn("only super-user can use -l option");
                        exit(EX_CANTCREAT);
                }
                dolmtp(bouncequota);
        }

        if (!*argv)
                usage();

        /*
         * 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 : "???";
        src_uid = pw ? pw->pw_uid : uid;

        /*
         * 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 (store(from, 0); *argv; ++argv)
                deliver(hfd, bfd, *argv, bouncequota);
        return (eval);
}

void
sigterm_handler()
{
        sigterm_caught = TRUE;
        (void) sigignore(SIGTERM);
}

char *
parseaddr(s)
        char *s;
{
        char *p;
        int len;

        if (*s++ != '<')
                return NULL;

        p = s;

        /* at-domain-list */
        while (*p == '@') {
                p++;
                if (*p == '[') {
                        p++;
                        while (isascii(*p) &&
                               (isalnum(*p) || *p == '.' ||
                                *p == '-' || *p == ':'))
                                p++;
                        if (*p++ != ']')
                                return NULL;
                } else {
                        while ((isascii(*p) && isalnum(*p)) ||
                               strchr(".-_", *p))
                                p++;
                }
                if (*p == ',' && p[1] == '@')
                        p++;
                else if (*p == ':' && p[1] != '@')
                        p++;
                else
                        return NULL;
        }

        s = p;

        /* local-part */
        if (*p == '\"') {
                p++;
                while (*p && *p != '\"') {
                        if (*p == '\\') {
                                if (!*++p)
                                        return NULL;
                        }
                        p++;
                }
                if (!*p++)
                        return NULL;
        } else {
                while (*p && *p != '@' && *p != '>') {
                        if (*p == '\\') {
                                if (!*++p)
                                        return NULL;
                        } else {
                        if (*p <= ' ' || (*p & 128) ||
                            strchr("<>()[]\\,;:\"", *p))
                                return NULL;
                        }
                        p++;
                }
        }

        /* @domain */
        if (*p == '@') {
                p++;
                if (*p == '[') {
                        p++;
                        while (isascii(*p) &&
                               (isalnum(*p) || *p == '.' ||
                                *p == '-' || *p == ':'))
                                p++;
                        if (*p++ != ']')
                                return NULL;
                } else {
                        while ((isascii(*p) && isalnum(*p)) ||
                               strchr(".-_", *p))
                                p++;
                }
        }

        if (*p++ != '>')
                return NULL;
        if (*p && *p != ' ')
                return NULL;
        len = p - s - 1;

        if (*s == '\0' || len <= 0)
        {
                s = MAILER_DAEMON;
                len = strlen(s);
        }

        p = malloc(len + 1);
        if (p == NULL) {
                printf("421 4.3.0 memory exhausted\r\n");
                exit(EX_TEMPFAIL);
        }

        strncpy(p, s, len);
        p[len] = '\0';
        return p;
}

char *
process_recipient(addr)
        char *addr;
{
        if (getpwnam(addr) == NULL) {
                return "550 5.1.1 user unknown";
        }

        return NULL;
}

#define RCPT_GROW       30

void
dolmtp(bouncequota)
        bool bouncequota;
{
        char *return_path = NULL;
        char **rcpt_addr = NULL;
        int rcpt_num = 0;
        int rcpt_alloc = 0;
        bool gotlhlo = FALSE;
        char myhostname[MAXHOSTNAMELEN];
        char buf[4096];
        char *err;
        char *p;
        int i;

        gethostname(myhostname, sizeof myhostname - 1);

        printf("220 %s LMTP ready\r\n", myhostname);
        for (;;) {
                if (sigterm_caught) {
                        for (; rcpt_num > 0; rcpt_num--)
                                printf("451 4.3.0 shutting down\r\n");
                        exit(EX_OK);
                }
                fflush(stdout);
                if (fgets(buf, sizeof(buf)-1, stdin) == NULL) {
                        exit(EX_OK);
                }
                p = buf + strlen(buf) - 1;
                if (p >= buf && *p == '\n')
                        *p-- = '\0';
                if (p >= buf && *p == '\r')
                        *p-- = '\0';

                switch (buf[0]) {

                case 'd':
                case 'D':
                        if (strcasecmp(buf, "data") == 0) {
                                if (rcpt_num == 0) {
                                        printf("503 5.5.1 No recipients\r\n");
                                        continue;
                                }
                                store(return_path, rcpt_num);
                                if (bfd == -1 || hfd == -1)
                                        continue;

                                for (i = 0; i < rcpt_num; i++) {
                                        p = strchr(rcpt_addr[i], '+');
                                        if (p != NULL)
                                                *p++ = '\0';
                                        deliver(hfd, bfd, rcpt_addr[i],
                                                bouncequota);
                                }
                                close(bfd);
                                close(hfd);
                                goto rset;
                        }
                        goto syntaxerr;
                        /* NOTREACHED */
                        break;

                case 'l':
                case 'L':
                        if (strncasecmp(buf, "lhlo ", 5) == 0)
                        {
                                /* check for duplicate per RFC 1651 4.2 */
                                if (gotlhlo)
                                {
                                        printf("503 %s Duplicate LHLO\r\n",
                                               myhostname);
                                        continue;
                                }
                                gotlhlo = TRUE;
                                printf("250-%s\r\n", myhostname);
                                if (EightBitMime)
                                        printf("250-8BITMIME\r\n");
                                printf("250-ENHANCEDSTATUSCODES\r\n");
                                printf("250 PIPELINING\r\n");
                                continue;
                        }
                        goto syntaxerr;
                        /* NOTREACHED */
                        break;

                case 'm':
                case 'M':
                        if (strncasecmp(buf, "mail ", 5) == 0) {
                                if (return_path != NULL) {
                                        printf("503 5.5.1 Nested MAIL command\r\n");
                                        continue;
                                }
                                if (strncasecmp(buf+5, "from:", 5) != 0 ||
                                    ((return_path = parseaddr(buf+10)) == NULL)) {
                                        printf("501 5.5.4 Syntax error in parameters\r\n");
                                        continue;
                                }
                                printf("250 2.5.0 ok\r\n");
                                continue;
                        }
                        goto syntaxerr;

                case 'n':
                case 'N':
                        if (strcasecmp(buf, "noop") == 0) {
                                printf("250 2.0.0 ok\r\n");
                                continue;
                        }
                        goto syntaxerr;

                case 'q':
                case 'Q':
                        if (strcasecmp(buf, "quit") == 0) {
                                printf("221 2.0.0 bye\r\n");
                                exit(EX_OK);
                        }
                        goto syntaxerr;

                case 'r':
                case 'R':
                        if (strncasecmp(buf, "rcpt ", 5) == 0) {
                                if (return_path == NULL) {
                                        printf("503 5.5.1 Need MAIL command\r\n");
                                        continue;
                                }
                                if (rcpt_num >= rcpt_alloc) {
                                        rcpt_alloc += RCPT_GROW;
                                        rcpt_addr = (char **)
                                                realloc((char *)rcpt_addr,
                                                        rcpt_alloc * sizeof(char **));
                                        if (rcpt_addr == NULL) {
                                                printf("421 4.3.0 memory exhausted\r\n");
                                                exit(EX_TEMPFAIL);
                                        }
                                }
                                if (strncasecmp(buf+5, "to:", 3) != 0 ||
                                    ((rcpt_addr[rcpt_num] = parseaddr(buf+8)) == NULL)) {
                                        printf("501 5.5.4 Syntax error in parameters\r\n");
                                        continue;
                                }
                                if ((err = process_recipient(rcpt_addr[rcpt_num])) != NULL) {
                                        printf("%s\r\n", err);
                                        continue;
                                }
                                rcpt_num++;
                                printf("250 2.1.5 ok\r\n");
                                continue;
                        }
                        else if (strcasecmp(buf, "rset") == 0) {
                                printf("250 2.0.0 ok\r\n");

  rset:
                                while (rcpt_num > 0) {
                                        free(rcpt_addr[--rcpt_num]);
                                }
                                if (return_path != NULL)
                                        free(return_path);
                                return_path = NULL;
                                continue;
                        }
                        goto syntaxerr;

                case 'v':
                case 'V':
                        if (strncasecmp(buf, "vrfy ", 5) == 0) {
                                printf("252 2.3.3 try RCPT to attempt delivery\r\n");
                                continue;
                        }
                        goto syntaxerr;

                default:
  syntaxerr:
                        printf("500 5.5.2 Syntax error\r\n");
                        continue;
                }
        }
}

static void
store(from, lmtprcpts)
        char *from;
        int lmtprcpts;
{
        FILE *fp = NULL;
        time_t tval;
        bool fullline = TRUE;   /* current line is terminated */
        bool prevfl;            /* previous line was terminated */
        char line[MAXLINE];
        FILE *bfp, *hfp;
        char *btn, *htn;
        int in_header_section;
        int newfd;

        bfd = -1;
        hfd = -1;
        btn = strdup(_PATH_LOCTMP);
        if ((bfd = mkstemp(btn)) == -1 || (bfp = fdopen(bfd, "w+")) == NULL) {
                if (bfd != -1)
                        (void) close(bfd);
                if (lmtprcpts) {
                        printf("451 4.3.0 unable to open temporary file\r\n");
                        return;
                } else {
                        mailerr("451 4.3.0", "unable to open temporary file");
                        exit(eval);
                }
        }
        (void) unlink(btn);
        free(btn);

        if (lmtpmode) {
                printf("354 go ahead\r\n");
                fflush(stdout);
        }

        htn = strdup(_PATH_LOCHTMP);
        if ((hfd = mkstemp(htn)) == -1 || (hfp = fdopen(hfd, "w+")) == NULL) {
                if (hfd != -1)
                        (void) close(hfd);
                e_to_sys(errno);
                err("unable to open temporary file");
        }
        (void) unlink(htn);
        free(htn);

        in_header_section = TRUE;
        content_length = 0;
        fp = hfp;

        line[0] = '\0';
        while (fgets(line, sizeof(line), stdin) != (char *)NULL)
        {
                size_t line_len = 0;
                int peek;

                prevfl = fullline;      /* preserve state of previous line */
                while (line[line_len] != '\n' && line_len < sizeof(line) - 2)
                        line_len++;
                line_len++;

                /* Check for dot-stuffing */
                if (prevfl && lmtprcpts && line[0] == '.')
                {
                        if (line[1] == '\n' ||
                            (line[1] == '\r' && line[2] == '\n'))
                                goto lmtpdot;
                        memcpy(line, line + 1, line_len);
                        line_len--;
                }

                /* Check to see if we have the full line from fgets() */
                fullline = FALSE;
                if (line_len > 0)
                {
                        if (line[line_len - 1] == '\n')
                        {
                                if (line_len >= 2 &&
                                    line[line_len - 2] == '\r')
                                {
                                        line[line_len - 2] = '\n';
                                        line[line_len - 1] = '\0';
                                        line_len--;
                                }
                                fullline = TRUE;
                        }
                        else if (line[line_len - 1] == '\r')
                        {
                                /* Did we just miss the CRLF? */
                                peek = fgetc(stdin);
                                if (peek == '\n')
                                {
                                        line[line_len - 1] = '\n';
                                        fullline = TRUE;
                                }
                                else
                                        (void) ungetc(peek, stdin);
                        }
                }
                else
                        fullline = TRUE;

                if (prevfl && line[0] == '\n' && in_header_section) {
                        in_header_section = FALSE;
                        if (fflush(fp) == EOF || ferror(fp)) {
                                if (lmtprcpts) {
                                        while (lmtprcpts--)
                                                printf("451 4.3.0 temporary file write error\r\n");
                                        fclose(fp);
                                        return;
                                } else {
                                        mailerr("451 4.3.0",
                                                "temporary file write error");
                                        fclose(fp);
                                        exit(eval);
                                }
                        }
                        fp = bfp;
                        continue;
                }

                if (in_header_section) {
                        if (strncasecmp("Content-Length:", line, 15) == 0) {
                                continue; /* skip this header */
                        }
                } else
                        content_length += strlen(line);
                (void) fwrite(line, sizeof(char), line_len, fp);
                if (ferror(fp)) {
                        if (lmtprcpts) {
                                while (lmtprcpts--)
                                        printf("451 4.3.0 temporary file write error\r\n");
                                fclose(fp);
                                return;
                        } else {
                                mailerr("451 4.3.0",
                                        "temporary file write error");
                                fclose(fp);
                                exit(eval);
                        }
                }
        }
        if (sigterm_caught) {
                if (lmtprcpts)
                        while (lmtprcpts--)
                                printf("451 4.3.0 shutting down\r\n");
                else
                        mailerr("451 4.3.0", "shutting down");
                fclose(fp);
                exit(eval);
        }

        if (lmtprcpts) {
                /* Got a premature EOF -- toss message and exit */
                exit(EX_OK);
        }

        /* If message not newline terminated, need an extra. */
        if (!strchr(line, '\n')) {
                (void) putc('\n', fp);
                content_length++;
        }

  lmtpdot:

        /* Output a newline; note, empty messages are allowed. */
        (void) putc('\n', fp);

        if (fflush(fp) == EOF || ferror(fp)) {
                if (lmtprcpts) {
                        while (lmtprcpts--) {
                                printf("451 4.3.0 temporary file write error\r\n");
                        }
                        fclose(fp);
                        return;
                } else {
                        mailerr("451 4.3.0", "temporary file write error");
                        fclose(fp);
                        exit(eval);
                }
        }

        if ((newfd = dup(bfd)) >= 0) {
                fclose(bfp);
                bfd = newfd;
        }
        if ((newfd = dup(hfd)) >= 0) {
                fclose(hfp);
                hfd = newfd;
        }
        (void) time(&tval);
        (void) snprintf(unix_from_line, sizeof (unix_from_line), "From %s %s",
            from, ctime(&tval));
        ulen = strlen(unix_from_line);
}

static void
handle_error(err_num, bouncequota, path)
        int err_num;
        bool bouncequota;
        char *path;
{
#ifdef EDQUOT
        if (err_num == EDQUOT && bouncequota) {
                mailerr("552 5.2.2", "%s: %s", path, sm_errstring(err_num));
        } else
#endif /* EDQUOT */
                mailerr("450 4.2.0", "%s: %s", path, sm_errstring(err_num));
}

static void
deliver(hfd, bfd, name, bouncequota)
        int hfd;
        int bfd;
        char *name;
        bool bouncequota;
{
        struct stat fsb, sb;
        int mbfd = -1, nr, nw = 0, off;
        char biffmsg[100], buf[8*1024], path[MAXPATHLEN];
        off_t curoff, cursize;
        int len;
        struct passwd *pw = NULL;

        /*
        * Disallow delivery to unknown names -- special mailboxes
        * can be handled in the sendmail aliases file.
        */
        if ((pw = getpwnam(name)) == NULL) {
                eval = EX_TEMPFAIL;
                mailerr("451 4.3.0", "cannot lookup name: %s", name);
                return;
        }
        endpwent();

        if (sigterm_caught) {
                mailerr("451 4.3.0", "shutting down");
                return;
        }

        /* mailbox may be NFS mounted, seteuid to user */
        targ_uid = pw->pw_uid;
        (void) seteuid(targ_uid);

        if ((saved_uid != 0) && (src_uid != targ_uid)) {
                /*
                 * If saved_uid == 0 (root), anything is OK; this is
                 * as it should be.  But to prevent a random user from
                 * calling "mail.local foo" in an attempt to hijack
                 * foo's mail-box, make sure src_uid == targ_uid o/w.
                 */
                warn("%s: wrong owner (is %d, should be %d)",
                        name, src_uid, targ_uid);
                eval = EX_CANTCREAT;
                return;
        }

        path[0] = '\0';
        (void) snprintf(path, sizeof (path), "%s/%s", _PATH_MAILDIR, name);

        /*
         * 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:
        /* should check lock status, but... maillock return no value */
        maillock(name, 10);

        if (sigterm_caught) {
                mailerr("451 4.3.0", "shutting down");
                goto err0;
        }

        if (lstat(path, &sb)) {
                mbfd = open(path, O_APPEND|O_CREAT|O_EXCL|O_WRONLY,
                                S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
                if (mbfd != -1)
                        (void) fchmod(mbfd, 0660);


                if (mbfd == -1) {
                        if (errno == EEXIST) {
                                mailunlock();
                                goto tryagain;
                        }
                }
        } else if (sb.st_nlink != 1) {
                mailerr("550 5.2.0", "%s: too many links", path);
                goto err0;
        } else if (!S_ISREG(sb.st_mode)) {
                mailerr("550 5.2.0", "%s: irregular file", path);
                goto err0;
        } 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)) {
                        eval = EX_TEMPFAIL;
                        mailerr("550 5.2.0",
                                "%s: fstat: file changed after open", path);
                        goto err1;
                }
        }

        if (mbfd == -1) {
                mailerr("450 4.2.0", "%s: %s", path, strerror(errno));
                goto err0;
        }

        if (sigterm_caught) {
                mailerr("451 4.3.0", "shutting down");
                goto err0;
        }

        /* Get the starting offset of the new message for biff. */
        curoff = lseek(mbfd, (off_t)0, SEEK_END);
        (void) snprintf(biffmsg, sizeof (biffmsg), "%s@%ld\n", name, curoff);

        /* Copy the message into the file. */
        if (lseek(hfd, (off_t)0, SEEK_SET) == (off_t)-1) {
                mailerr("450 4.2.0", "temporary file: %s", strerror(errno));
                goto err1;
        }
        /* Copy the message into the file. */
        if (lseek(bfd, (off_t)0, SEEK_SET) == (off_t)-1) {
                mailerr("450 4.2.0", "temporary file: %s", strerror(errno));
                goto err1;
        }
        if ((write(mbfd, unix_from_line, ulen)) != ulen) {
                handle_error(errno, bouncequota, path);
                goto err2;
        }

        if (sigterm_caught) {
                mailerr("451 4.3.0", "shutting down");
                goto err2;
        }

        while ((nr = read(hfd, buf, sizeof (buf))) > 0)
                for (off = 0; off < nr; nr -= nw, off += nw)
                        if ((nw = write(mbfd, buf + off, nr)) < 0)
                        {
                                handle_error(errno, bouncequota, path);
                                goto err2;
                        }
        if (nr < 0) {
                handle_error(errno, bouncequota, path);
                goto err2;
        }

        if (sigterm_caught) {
                mailerr("451 4.3.0", "shutting down");
                goto err2;
        }

        (void) snprintf(buf, sizeof (buf), "Content-Length: %d\n\n",
            content_length);
        len = strlen(buf);
        if (write(mbfd, buf, len) != len) {
                handle_error(errno, bouncequota, path);
                goto err2;
        }

        if (sigterm_caught) {
                mailerr("451 4.3.0", "shutting down");
                goto err2;
        }

        while ((nr = read(bfd, buf, sizeof (buf))) > 0) {
                for (off = 0; off < nr; nr -= nw, off += nw)
                        if ((nw = write(mbfd, buf + off, nr)) < 0) {
                                handle_error(errno, bouncequota, path);
                                goto err2;
                        }
                if (sigterm_caught) {
                        mailerr("451 4.3.0", "shutting down");
                        goto err2;
                }
        }
        if (nr < 0) {
                handle_error(errno, bouncequota, path);
                goto err2;
        }

        /* Flush to disk, don't wait for update. */
        if (fsync(mbfd)) {
                handle_error(errno, bouncequota, path);
err2:           if (mbfd >= 0)
                        (void)ftruncate(mbfd, curoff);
err1:           (void)close(mbfd);
err0:           mailunlock();
                (void)seteuid(saved_uid);
                return;
        }

        /*
        **  Save the current size so if the close() fails below
        **  we can make sure no other process has changed the mailbox
        **  between the failed close and the re-open()/re-lock().
        **  If something else has changed the size, we shouldn't
        **  try to truncate it as we may do more harm then good
        **  (e.g., truncate a later message delivery).
        */

        if (fstat(mbfd, &sb) < 0)
                cursize = 0;
        else
                cursize = sb.st_size;

        /* Close and check -- NFS doesn't write until the close. */
        if (close(mbfd))
        {
                handle_error(errno, bouncequota, path);
                mbfd = open(path, O_WRONLY, 0);
                if (mbfd < 0 ||
                    cursize == 0
                    || flock(mbfd, LOCK_EX) < 0 ||
                    fstat(mbfd, &sb) < 0 ||
                    sb.st_size != cursize ||
                    sb.st_nlink != 1 ||
                    !S_ISREG(sb.st_mode) ||
                    sb.st_dev != fsb.st_dev ||
                    sb.st_ino != fsb.st_ino ||
                    sb.st_uid != fsb.st_uid)
                {
                        /* Don't use a bogus file */
                        if (mbfd >= 0)
                        {
                                (void) close(mbfd);
                                mbfd = -1;
                        }
                }

                /* Attempt to truncate back to pre-write size */
                goto err2;
        } else
                notifybiff(biffmsg);

        mailunlock();

        (void)seteuid(saved_uid);

        if (lmtpmode) {
                printf("250 2.1.5 %s OK\r\n", name);
        }
}

static void
notifybiff(msg)
        char *msg;
{
        static struct sockaddr_in addr;
        static int f = -1;
        struct hostent *hp;
        struct servent *sp;
        int len;

        if (msg == NULL) {
                /* Be silent if biff service not available. */
                if ((sp = getservbyname("biff", "udp")) == NULL)
                        return;
                if ((hp = gethostbyname("localhost")) == NULL) {
                        warn("localhost: %s", strerror(errno));
                        return;
                }
                addr.sin_family = hp->h_addrtype;
                (void) memmove(&addr.sin_addr, hp->h_addr, hp->h_length);
                addr.sin_port = sp->s_port;
                return;
        }

        if (addr.sin_family == 0)
                return; /* did not initialize */

        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));
}

static void
usage()
{
        eval = EX_USAGE;
        err("usage: mail.local [-l] [-f from] user ...");
}

static void
/*VARARGS2*/
#ifdef __STDC__
mailerr(const char *hdr, const char *fmt, ...)
#else
mailerr(hdr, fmt, va_alist)
        const char *hdr;
        const char *fmt;
        va_dcl
#endif
{
        va_list ap;

#ifdef __STDC__
        va_start(ap, fmt);
#else
        va_start(ap);
#endif
        if (lmtpmode)
        {
                if (hdr != NULL)
                        printf("%s ", hdr);
                vprintf(fmt, ap);
                printf("\r\n");
        }
        else
        {
                e_to_sys(errno);
                vwarn(fmt, ap);
        }
}

static void
/*VARARGS1*/
#ifdef __STDC__
err(const char *fmt, ...)
#else
err(fmt, va_alist)
        const char *fmt;
        va_dcl
#endif
{
        va_list ap;

#ifdef __STDC__
        va_start(ap, fmt);
#else
        va_start(ap);
#endif
        vwarn(fmt, ap);
        va_end(ap);

        exit(eval);
}

static void
/*VARARGS1*/
#ifdef __STDC__
warn(const char *fmt, ...)
#else
warn(fmt, va_alist)
        const char *fmt;
        va_dcl
#endif
{
        va_list ap;

#ifdef __STDC__
        va_start(ap, fmt);
#else
        va_start(ap);
#endif
        vwarn(fmt, ap);
        va_end(ap);
}

static void
vwarn(fmt, ap)
        const char *fmt;
        va_list ap;
{
        /*
         * 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);
}

/*
 * e_to_sys --
 *      Guess which errno's are temporary.  Gag me.
 */
static void
e_to_sys(num)
        int num;
{
        /* Temporary failures override hard errors. */
        if (eval == EX_TEMPFAIL)
                return;

        switch (num)            /* Hopefully temporary errors. */
        {
#ifdef EDQUOT
        case EDQUOT:            /* Disc quota exceeded */
                if (bouncequota)
                {
                        eval = EX_UNAVAILABLE;
                        break;
                }
#endif /* EDQUOT */
#ifdef EAGAIN
                /* FALLTHROUGH */
        case EAGAIN:            /* Resource temporarily unavailable */
#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;
        }
}