BSD 4_3_Net_2 release
[unix-history] / usr / src / usr.sbin / sendmail / src / queue.c
index 9648219..03b226a 100644 (file)
@@ -1,29 +1,55 @@
 /*
 /*
-**  Sendmail
-**  Copyright (c) 1983  Eric P. Allman
-**  Berkeley, California
-**
-**  Copyright (c) 1983 Regents of the University of California.
-**  All rights reserved.  The Berkeley software License Agreement
-**  specifies the terms and conditions for redistribution.
-*/
-
+ * Copyright (c) 1983 Eric P. Allman
+ * Copyright (c) 1988 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.
+ */
 
 # include "sendmail.h"
 
 # include "sendmail.h"
+
+#ifndef lint
+#ifdef QUEUE
+static char sccsid[] = "@(#)queue.c    5.32 (Berkeley) 3/12/91 (with queueing)";
+#else
+static char sccsid[] = "@(#)queue.c    5.32 (Berkeley) 3/12/91 (without queueing)";
+#endif
+#endif /* not lint */
+
 # include <sys/stat.h>
 # include <sys/dir.h>
 # include <sys/stat.h>
 # include <sys/dir.h>
+# include <sys/file.h>
 # include <signal.h>
 # include <errno.h>
 # include <signal.h>
 # include <errno.h>
+# include <pwd.h>
 
 
-# ifndef QUEUE
-# ifndef lint
-static char    SccsId[] = "@(#)queue.c 5.4 (Berkeley) %G%      (no queueing)";
-# endif not lint
-# else QUEUE
-
-# ifndef lint
-static char    SccsId[] = "@(#)queue.c 5.4 (Berkeley) %G%";
-# endif not lint
+# ifdef QUEUE
 
 /*
 **  Work queue.
 
 /*
 **  Work queue.
@@ -33,10 +59,12 @@ struct work
 {
        char            *w_name;        /* name of control file */
        long            w_pri;          /* priority of message, see below */
 {
        char            *w_name;        /* name of control file */
        long            w_pri;          /* priority of message, see below */
+       time_t          w_ctime;        /* creation time of message */
        struct work     *w_next;        /* next in queue */
 };
 
 typedef struct work    WORK;
        struct work     *w_next;        /* next in queue */
 };
 
 typedef struct work    WORK;
+extern int la;
 
 WORK   *WorkQ;                 /* queue of things to be done */
 \f/*
 
 WORK   *WorkQ;                 /* queue of things to be done */
 \f/*
@@ -49,42 +77,53 @@ WORK        *WorkQ;                 /* queue of things to be done */
 **             announce -- if TRUE, tell when you are queueing up.
 **
 **     Returns:
 **             announce -- if TRUE, tell when you are queueing up.
 **
 **     Returns:
-**             none.
+**             locked FILE* to q file
 **
 **     Side Effects:
 **             The current request are saved in a control file.
 */
 
 **
 **     Side Effects:
 **             The current request are saved in a control file.
 */
 
+FILE *
 queueup(e, queueall, announce)
        register ENVELOPE *e;
        bool queueall;
        bool announce;
 {
 queueup(e, queueall, announce)
        register ENVELOPE *e;
        bool queueall;
        bool announce;
 {
-       char *tf;
        char *qf;
        char *qf;
-       char buf[MAXLINE];
+       char buf[MAXLINE], tf[MAXLINE];
        register FILE *tfp;
        register HDR *h;
        register ADDRESS *q;
        MAILER nullmailer;
        register FILE *tfp;
        register HDR *h;
        register ADDRESS *q;
        MAILER nullmailer;
+       int fd, ret;
 
        /*
        **  Create control file.
        */
 
 
        /*
        **  Create control file.
        */
 
-       tf = newstr(queuename(e, 't'));
-       tfp = fopen(tf, "w");
-       if (tfp == NULL)
-       {
-               syserr("queueup: cannot create temp file %s", tf);
-               return;
-       }
-       (void) chmod(tf, FileMode);
+       do {
+               strcpy(tf, queuename(e, 't'));
+               fd = open(tf, O_CREAT|O_WRONLY|O_EXCL, FileMode);
+               if (fd < 0) {
+                       if ( errno != EEXIST) {
+                               syserr("queueup: cannot create temp file %s",
+                                       tf);
+                               return NULL;
+                       }
+               } else {
+                       if (flock(fd, LOCK_EX|LOCK_NB) < 0) {
+                               if (errno != EWOULDBLOCK)
+                                       syserr("cannot flock(%s)", tf);
+                               close(fd);
+                               fd = -1;
+                       }
+               }
+       } while (fd < 0);
+
+       tfp = fdopen(fd, "w");
 
 
-# ifdef DEBUG
        if (tTd(40, 1))
                printf("queueing %s\n", e->e_id);
        if (tTd(40, 1))
                printf("queueing %s\n", e->e_id);
-# endif DEBUG
 
        /*
        **  If there is no data file yet, create one.
 
        /*
        **  If there is no data file yet, create one.
@@ -96,14 +135,14 @@ queueup(e, queueall, announce)
                extern putbody();
 
                e->e_df = newstr(queuename(e, 'd'));
                extern putbody();
 
                e->e_df = newstr(queuename(e, 'd'));
-               dfp = fopen(e->e_df, "w");
-               if (dfp == NULL)
+               fd = open(e->e_df, O_WRONLY|O_CREAT, FileMode);
+               if (fd < 0)
                {
                        syserr("queueup: cannot create %s", e->e_df);
                        (void) fclose(tfp);
                {
                        syserr("queueup: cannot create %s", e->e_df);
                        (void) fclose(tfp);
-                       return;
+                       return NULL;
                }
                }
-               (void) chmod(e->e_df, FileMode);
+               dfp = fdopen(fd, "w");
                (*e->e_putbody)(dfp, ProgMailer, e);
                (void) fclose(dfp);
                e->e_putbody = putbody;
                (*e->e_putbody)(dfp, ProgMailer, e);
                (void) fclose(dfp);
                e->e_putbody = putbody;
@@ -111,7 +150,8 @@ queueup(e, queueall, announce)
 
        /*
        **  Output future work requests.
 
        /*
        **  Output future work requests.
-       **      Priority should be first, since it is read by orderq.
+       **      Priority and creation time should be first, since
+       **      they are required by orderq.
        */
 
        /* output message priority */
        */
 
        /* output message priority */
@@ -133,9 +173,13 @@ queueup(e, queueall, announce)
        /* output list of recipient addresses */
        for (q = e->e_sendqueue; q != NULL; q = q->q_next)
        {
        /* output list of recipient addresses */
        for (q = e->e_sendqueue; q != NULL; q = q->q_next)
        {
-               if (queueall ? !bitset(QDONTSEND, q->q_flags) :
+               if (queueall ? !bitset(QDONTSEND|QSENT, q->q_flags) :
                               bitset(QQUEUEUP, q->q_flags))
                {
                               bitset(QQUEUEUP, q->q_flags))
                {
+                       char *ctluser, *getctluser();
+
+                       if ((ctluser = getctluser(q)) != NULL)
+                               fprintf(tfp, "C%s\n", ctluser);
                        fprintf(tfp, "R%s\n", q->q_paddr);
                        if (announce)
                        {
                        fprintf(tfp, "R%s\n", q->q_paddr);
                        if (announce)
                        {
@@ -145,13 +189,24 @@ queueup(e, queueall, announce)
                                        logdelivery("queued");
                                e->e_to = NULL;
                        }
                                        logdelivery("queued");
                                e->e_to = NULL;
                        }
-#ifdef DEBUG
                        if (tTd(40, 1))
                        {
                                printf("queueing ");
                                printaddr(q, FALSE);
                        }
                        if (tTd(40, 1))
                        {
                                printf("queueing ");
                                printaddr(q, FALSE);
                        }
-#endif DEBUG
+               }
+       }
+
+       /* output list of error recipients */
+       for (q = e->e_errorqueue; q != NULL; q = q->q_next)
+       {
+               if (!bitset(QDONTSEND, q->q_flags))
+               {
+                       char *ctluser, *getctluser();
+
+                       if ((ctluser = getctluser(q)) != NULL)
+                               fprintf(tfp, "C%s\n", ctluser);
+                       fprintf(tfp, "E%s\n", q->q_paddr);
                }
        }
 
                }
        }
 
@@ -216,24 +271,18 @@ queueup(e, queueall, announce)
        **  Clean up.
        */
 
        **  Clean up.
        */
 
-       (void) fclose(tfp);
        qf = queuename(e, 'q');
        qf = queuename(e, 'q');
-       if (tf != NULL)
-       {
-               holdsigs();
-               (void) unlink(qf);
-               if (link(tf, qf) < 0)
-                       syserr("cannot link(%s, %s), df=%s", tf, qf, e->e_df);
-               else
-                       (void) unlink(tf);
-               rlsesigs();
-       }
+       if (rename(tf, qf) < 0)
+               syserr("cannot rename(%s, %s), df=%s", tf, qf, e->e_df);
+       errno = 0;
 
 # ifdef LOG
        /* save log info */
        if (LogLevel > 15)
                syslog(LOG_DEBUG, "%s: queueup, qf=%s, df=%s\n", e->e_id, qf, e->e_df);
 # endif LOG
 
 # ifdef LOG
        /* save log info */
        if (LogLevel > 15)
                syslog(LOG_DEBUG, "%s: queueup, qf=%s, df=%s\n", e->e_id, qf, e->e_df);
 # endif LOG
+       fflush(tfp);
+       return tfp;
 }
 \f/*
 **  RUNQUEUE -- run the jobs in the queue.
 }
 \f/*
 **  RUNQUEUE -- run the jobs in the queue.
@@ -242,7 +291,9 @@ queueup(e, queueall, announce)
 **     order and processes them.
 **
 **     Parameters:
 **     order and processes them.
 **
 **     Parameters:
-**             none.
+**             forkflag -- TRUE if the queue scanning should be done in
+**                     a child process.  We double-fork so it is not our
+**                     child and we don't have to clean up after it.
 **
 **     Returns:
 **             none.
 **
 **     Returns:
 **             none.
@@ -254,6 +305,25 @@ queueup(e, queueall, announce)
 runqueue(forkflag)
        bool forkflag;
 {
 runqueue(forkflag)
        bool forkflag;
 {
+       extern bool shouldqueue();
+
+       /*
+       **  If no work will ever be selected, don't even bother reading
+       **  the queue.
+       */
+
+       la = getla();   /* get load average */
+
+       if (shouldqueue(-100000000L))
+       {
+               if (Verbose)
+                       printf("Skipping queue run -- load average too high\n");
+
+               if (forkflag)
+                       return;
+               finis();
+       }
+
        /*
        **  See if we want to go off and do other useful work.
        */
        /*
        **  See if we want to go off and do other useful work.
        */
@@ -265,16 +335,29 @@ runqueue(forkflag)
                pid = dofork();
                if (pid != 0)
                {
                pid = dofork();
                if (pid != 0)
                {
+                       extern void reapchild();
+
                        /* parent -- pick up intermediate zombie */
                        /* parent -- pick up intermediate zombie */
+#ifndef SIGCHLD
                        (void) waitfor(pid);
                        (void) waitfor(pid);
+#else SIGCHLD
+                       (void) signal(SIGCHLD, reapchild);
+#endif SIGCHLD
                        if (QueueIntvl != 0)
                                (void) setevent(QueueIntvl, runqueue, TRUE);
                        return;
                }
                /* child -- double fork */
                        if (QueueIntvl != 0)
                                (void) setevent(QueueIntvl, runqueue, TRUE);
                        return;
                }
                /* child -- double fork */
+#ifndef SIGCHLD
                if (fork() != 0)
                        exit(EX_OK);
                if (fork() != 0)
                        exit(EX_OK);
+#else SIGCHLD
+               (void) signal(SIGCHLD, SIG_DFL);
+#endif SIGCHLD
        }
        }
+
+       setproctitle("running queue: %s", QueueDir);
+
 # ifdef LOG
        if (LogLevel > 11)
                syslog(LOG_DEBUG, "runqueue %s, pid=%d", QueueDir, getpid());
 # ifdef LOG
        if (LogLevel > 11)
                syslog(LOG_DEBUG, "runqueue %s, pid=%d", QueueDir, getpid());
@@ -288,6 +371,12 @@ runqueue(forkflag)
        clrdaemon();
 # endif DAEMON
 
        clrdaemon();
 # endif DAEMON
 
+       /*
+       **  Make sure the alias database is open.
+       */
+
+       initaliases(AliasFile, FALSE);
+
        /*
        **  Start making passes through the queue.
        **      First, read and sort the entire queue.
        /*
        **  Start making passes through the queue.
        **      First, read and sort the entire queue.
@@ -296,7 +385,7 @@ runqueue(forkflag)
        */
 
        /* order the existing work requests */
        */
 
        /* order the existing work requests */
-       (void) orderq();
+       (void) orderq(FALSE);
 
        /* process them once at a time */
        while (WorkQ != NULL)
 
        /* process them once at a time */
        while (WorkQ != NULL)
@@ -308,13 +397,18 @@ runqueue(forkflag)
                free(w->w_name);
                free((char *) w);
        }
                free(w->w_name);
                free((char *) w);
        }
-       finis();
+
+       /* exit without the usual cleanup */
+       exit(ExitStat);
 }
 \f/*
 **  ORDERQ -- order the work queue.
 **
 **     Parameters:
 }
 \f/*
 **  ORDERQ -- order the work queue.
 **
 **     Parameters:
-**             none.
+**             doall -- if set, include everything in the queue (even
+**                     the jobs that cannot be run because the load
+**                     average is too high).  Otherwise, exclude those
+**                     jobs.
 **
 **     Returns:
 **             The number of request in the queue (not necessarily
 **
 **     Returns:
 **             The number of request in the queue (not necessarily
@@ -324,25 +418,17 @@ runqueue(forkflag)
 **             Sets WorkQ to the queue of available work, in order.
 */
 
 **             Sets WorkQ to the queue of available work, in order.
 */
 
-# define WLSIZE                120     /* max size of worklist per sort */
+# define NEED_P                001
+# define NEED_T                002
 
 
-# ifndef DIR
-# define DIR           FILE
-# define direct                dir
-# define opendir(d)    fopen(d, "r")
-# define readdir(f)    ((fread(&dbuf, sizeof dbuf, 1, f) > 0) ? &dbuf : 0)
-static struct dir      dbuf;
-# define closedir(f)   fclose(f)
-# endif DIR
-
-orderq()
+orderq(doall)
+       bool doall;
 {
        register struct direct *d;
        register WORK *w;
 {
        register struct direct *d;
        register WORK *w;
-       register WORK **wp;             /* parent of w */
        DIR *f;
        register int i;
        DIR *f;
        register int i;
-       WORK wlist[WLSIZE+1];
+       WORK wlist[QUEUESIZE+1];
        int wn = -1;
        extern workcmpf();
 
        int wn = -1;
        extern workcmpf();
 
@@ -375,44 +461,57 @@ orderq()
                char lbuf[MAXNAME];
 
                /* is this an interesting entry? */
                char lbuf[MAXNAME];
 
                /* is this an interesting entry? */
-               if (d->d_ino == 0)
-                       continue;
-# ifdef DEBUG
-               if (tTd(40, 10))
-                       printf("orderq: %12s\n", d->d_name);
-# endif DEBUG
                if (d->d_name[0] != 'q' || d->d_name[1] != 'f')
                        continue;
 
                /* yes -- open control file (if not too many files) */
                if (d->d_name[0] != 'q' || d->d_name[1] != 'f')
                        continue;
 
                /* yes -- open control file (if not too many files) */
-               if (++wn >= WLSIZE)
+               if (++wn >= QUEUESIZE)
                        continue;
                cf = fopen(d->d_name, "r");
                if (cf == NULL)
                {
                        /* this may be some random person sending hir msgs */
                        /* syserr("orderq: cannot open %s", cbuf); */
                        continue;
                cf = fopen(d->d_name, "r");
                if (cf == NULL)
                {
                        /* this may be some random person sending hir msgs */
                        /* syserr("orderq: cannot open %s", cbuf); */
-#ifdef DEBUG
                        if (tTd(41, 2))
                                printf("orderq: cannot open %s (%d)\n",
                                        d->d_name, errno);
                        if (tTd(41, 2))
                                printf("orderq: cannot open %s (%d)\n",
                                        d->d_name, errno);
-#endif DEBUG
                        errno = 0;
                        wn--;
                        continue;
                }
                        errno = 0;
                        wn--;
                        continue;
                }
-               wlist[wn].w_name = newstr(d->d_name);
+               w = &wlist[wn];
+               w->w_name = newstr(d->d_name);
+
+               /* make sure jobs in creation don't clog queue */
+               w->w_pri = 0x7fffffff;
+               w->w_ctime = 0;
 
                /* extract useful information */
 
                /* extract useful information */
-               while (fgets(lbuf, sizeof lbuf, cf) != NULL)
+               i = NEED_P | NEED_T;
+               while (i != 0 && fgets(lbuf, sizeof lbuf, cf) != NULL)
                {
                {
-                       if (lbuf[0] == 'P')
+                       extern long atol();
+
+                       switch (lbuf[0])
                        {
                        {
-                               (void) sscanf(&lbuf[1], "%ld", &wlist[wn].w_pri);
+                         case 'P':
+                               w->w_pri = atol(&lbuf[1]);
+                               i &= ~NEED_P;
+                               break;
+
+                         case 'T':
+                               w->w_ctime = atol(&lbuf[1]);
+                               i &= ~NEED_T;
                                break;
                        }
                }
                (void) fclose(cf);
                                break;
                        }
                }
                (void) fclose(cf);
+
+               if (!doall && shouldqueue(w->w_pri))
+               {
+                       /* don't even bother sorting this job in */
+                       wn--;
+               }
        }
        (void) closedir(f);
        wn++;
        }
        (void) closedir(f);
        wn++;
@@ -421,31 +520,29 @@ orderq()
        **  Sort the work directory.
        */
 
        **  Sort the work directory.
        */
 
-       qsort((char *) wlist, min(wn, WLSIZE), sizeof *wlist, workcmpf);
+       qsort((char *) wlist, min(wn, QUEUESIZE), sizeof *wlist, workcmpf);
 
        /*
        **  Convert the work list into canonical form.
        **      Should be turning it into a list of envelopes here perhaps.
        */
 
 
        /*
        **  Convert the work list into canonical form.
        **      Should be turning it into a list of envelopes here perhaps.
        */
 
-       wp = &WorkQ;
-       for (i = min(wn, WLSIZE); --i >= 0; )
+       WorkQ = NULL;
+       for (i = min(wn, QUEUESIZE); --i >= 0; )
        {
                w = (WORK *) xalloc(sizeof *w);
                w->w_name = wlist[i].w_name;
                w->w_pri = wlist[i].w_pri;
        {
                w = (WORK *) xalloc(sizeof *w);
                w->w_name = wlist[i].w_name;
                w->w_pri = wlist[i].w_pri;
-               w->w_next = NULL;
-               *wp = w;
-               wp = &w->w_next;
+               w->w_ctime = wlist[i].w_ctime;
+               w->w_next = WorkQ;
+               WorkQ = w;
        }
 
        }
 
-# ifdef DEBUG
        if (tTd(40, 1))
        {
                for (w = WorkQ; w != NULL; w = w->w_next)
                        printf("%32s: pri=%ld\n", w->w_name, w->w_pri);
        }
        if (tTd(40, 1))
        {
                for (w = WorkQ; w != NULL; w = w->w_next)
                        printf("%32s: pri=%ld\n", w->w_name, w->w_pri);
        }
-# endif DEBUG
 
        return (wn);
 }
 
        return (wn);
 }
@@ -457,9 +554,9 @@ orderq()
 **             b -- the second argument.
 **
 **     Returns:
 **             b -- the second argument.
 **
 **     Returns:
-**             1 if a < b
-**             0 if a == b
-**             -1 if a > b
+**             -1 if a < b
+**              0 if a == b
+**             +1 if a > b
 **
 **     Side Effects:
 **             none.
 **
 **     Side Effects:
 **             none.
@@ -469,12 +566,15 @@ workcmpf(a, b)
        register WORK *a;
        register WORK *b;
 {
        register WORK *a;
        register WORK *b;
 {
-       if (a->w_pri == b->w_pri)
+       long pa = a->w_pri + a->w_ctime;
+       long pb = b->w_pri + b->w_ctime;
+
+       if (pa == pb)
                return (0);
                return (0);
-       else if (a->w_pri > b->w_pri)
-               return (-1);
-       else
+       else if (pa > pb)
                return (1);
                return (1);
+       else
+               return (-1);
 }
 \f/*
 **  DOWORK -- do a work request.
 }
 \f/*
 **  DOWORK -- do a work request.
@@ -493,25 +593,43 @@ dowork(w)
        register WORK *w;
 {
        register int i;
        register WORK *w;
 {
        register int i;
+       extern bool shouldqueue();
 
 
-# ifdef DEBUG
        if (tTd(40, 1))
                printf("dowork: %s pri %ld\n", w->w_name, w->w_pri);
        if (tTd(40, 1))
                printf("dowork: %s pri %ld\n", w->w_name, w->w_pri);
-# endif DEBUG
 
        /*
 
        /*
-       **  Fork for work.
+       **  Ignore jobs that are too expensive for the moment.
        */
 
        */
 
-       i = fork();
-       if (i < 0)
+       if (shouldqueue(w->w_pri))
        {
        {
-               syserr("dowork: cannot fork");
+               if (Verbose)
+                       printf("\nSkipping %s\n", w->w_name + 2);
                return;
        }
 
                return;
        }
 
+       /*
+       **  Fork for work.
+       */
+
+       if (ForkQueueRuns)
+       {
+               i = fork();
+               if (i < 0)
+               {
+                       syserr("dowork: cannot fork");
+                       return;
+               }
+       }
+       else
+       {
+               i = 0;
+       }
+
        if (i == 0)
        {
        if (i == 0)
        {
+               FILE *qflock, *readqf();
                /*
                **  CHILD
                **      Lock the control file to avoid duplicate deliveries.
                /*
                **  CHILD
                **      Lock the control file to avoid duplicate deliveries.
@@ -522,8 +640,7 @@ dowork(w)
 
                /* set basic modes, etc. */
                (void) alarm(0);
 
                /* set basic modes, etc. */
                (void) alarm(0);
-               closexscript(CurEnv);
-               CurEnv->e_flags &= ~EF_FATALERRS;
+               clearenvelope(CurEnv, FALSE);
                QueueRun = TRUE;
                ErrorMode = EM_MAIL;
                CurEnv->e_id = &w->w_name[2];
                QueueRun = TRUE;
                ErrorMode = EM_MAIL;
                CurEnv->e_id = &w->w_name[2];
@@ -536,22 +653,16 @@ dowork(w)
                /* don't use the headers from sendmail.cf... */
                CurEnv->e_header = NULL;
 
                /* don't use the headers from sendmail.cf... */
                CurEnv->e_header = NULL;
 
-               /* lock the control file during processing */
-               if (link(w->w_name, queuename(CurEnv, 'l')) < 0)
+               /* read the queue control file */
+               /*  and lock the control file during processing */
+               if ((qflock=readqf(CurEnv, TRUE)) == NULL)
                {
                {
-                       /* being processed by another queuer */
-# ifdef LOG
-                       if (LogLevel > 4)
-                               syslog(LOG_DEBUG, "%s: locked", CurEnv->e_id);
-# endif LOG
-                       exit(EX_OK);
+                       if (ForkQueueRuns)
+                               exit(EX_OK);
+                       else
+                               return;
                }
 
                }
 
-               /* do basic system initialization */
-               initsys();
-
-               /* read the queue control file */
-               readqf(CurEnv, TRUE);
                CurEnv->e_flags |= EF_INQUEUE;
                eatheader(CurEnv);
 
                CurEnv->e_flags |= EF_INQUEUE;
                eatheader(CurEnv);
 
@@ -560,15 +671,21 @@ dowork(w)
                        sendall(CurEnv, SM_DELIVER);
 
                /* finish up and exit */
                        sendall(CurEnv, SM_DELIVER);
 
                /* finish up and exit */
-               finis();
+               if (ForkQueueRuns)
+                       finis();
+               else
+                       dropenvelope(CurEnv);
+               fclose(qflock);
        }
        }
+       else
+       {
+               /*
+               **  Parent -- pick up results.
+               */
 
 
-       /*
-       **  Parent -- pick up results.
-       */
-
-       errno = 0;
-       (void) waitfor(i);
+               errno = 0;
+               (void) waitfor(i);
+       }
 }
 \f/*
 **  READQF -- read queue file and set up environment.
 }
 \f/*
 **  READQF -- read queue file and set up environment.
@@ -579,13 +696,15 @@ dowork(w)
 **                     read in info needed for a queue print.
 **
 **     Returns:
 **                     read in info needed for a queue print.
 **
 **     Returns:
-**             none.
+**             FILE * pointing to flock()ed fd so it can be closed
+**             after the mail is delivered
 **
 **     Side Effects:
 **             cf is read and created as the current job, as though
 **             we had been invoked by argument.
 */
 
 **
 **     Side Effects:
 **             cf is read and created as the current job, as though
 **             we had been invoked by argument.
 */
 
+FILE *
 readqf(e, full)
        register ENVELOPE *e;
        bool full;
 readqf(e, full)
        register ENVELOPE *e;
        bool full;
@@ -594,7 +713,9 @@ readqf(e, full)
        register FILE *qfp;
        char buf[MAXFIELD];
        extern char *fgetfolded();
        register FILE *qfp;
        char buf[MAXFIELD];
        extern char *fgetfolded();
-       extern ADDRESS *sendto();
+       extern long atol();
+       int gotctluser = 0;
+       int fd;
 
        /*
        **  Read and process the file.
 
        /*
        **  Read and process the file.
@@ -604,19 +725,46 @@ readqf(e, full)
        qfp = fopen(qf, "r");
        if (qfp == NULL)
        {
        qfp = fopen(qf, "r");
        if (qfp == NULL)
        {
-               syserr("readqf: no control file %s", qf);
-               return;
+               if (errno != ENOENT)
+                       syserr("readqf: no control file %s", qf);
+               return NULL;
+       }
+
+       if (flock(fileno(qfp), LOCK_EX|LOCK_NB) < 0)
+       {
+# ifdef LOG
+               /* being processed by another queuer */
+               if (Verbose)
+                       printf("%s: locked\n", CurEnv->e_id);
+# endif LOG
+               (void) fclose(qfp);
+               return NULL;
        }
        }
+
+       /* do basic system initialization */
+       initsys();
+
        FileName = qf;
        LineNumber = 0;
        if (Verbose && full)
                printf("\nRunning %s\n", e->e_id);
        while (fgetfolded(buf, sizeof buf, qfp) != NULL)
        {
        FileName = qf;
        LineNumber = 0;
        if (Verbose && full)
                printf("\nRunning %s\n", e->e_id);
        while (fgetfolded(buf, sizeof buf, qfp) != NULL)
        {
+               if (tTd(40, 4))
+                       printf("+++++ %s\n", buf);
                switch (buf[0])
                {
                switch (buf[0])
                {
+                 case 'C':             /* specify controlling user */
+                       setctluser(&buf[1]);
+                       gotctluser = 1;
+                       break;
+
                  case 'R':             /* specify recipient */
                  case 'R':             /* specify recipient */
-                       (void) sendto(&buf[1], 1, (ADDRESS *) NULL, 0);
+                       sendtolist(&buf[1], (ADDRESS *) NULL, &e->e_sendqueue);
+                       break;
+
+                 case 'E':             /* specify error recipient */
+                       sendtolist(&buf[1], (ADDRESS *) NULL, &e->e_errorqueue);
                        break;
 
                  case 'H':             /* header */
                        break;
 
                  case 'H':             /* header */
@@ -642,23 +790,53 @@ readqf(e, full)
                        break;
 
                  case 'T':             /* init time */
                        break;
 
                  case 'T':             /* init time */
-                       (void) sscanf(&buf[1], "%ld", &e->e_ctime);
+                       e->e_ctime = atol(&buf[1]);
                        break;
 
                  case 'P':             /* message priority */
                        break;
 
                  case 'P':             /* message priority */
-                       (void) sscanf(&buf[1], "%ld", &e->e_msgpriority);
+                       e->e_msgpriority = atol(&buf[1]) + WkTimeFact;
+                       break;
 
 
-                       /* make sure that big things get sent eventually */
-                       e->e_msgpriority -= WKTIMEFACT;
+                 case '\0':            /* blank line; ignore */
                        break;
 
                  default:
                        break;
 
                  default:
-                       syserr("readqf(%s): bad line \"%s\"", e->e_id, buf);
+                       syserr("readqf(%s:%d): bad line \"%s\"", e->e_id,
+                               LineNumber, buf);
                        break;
                }
                        break;
                }
+               /*
+               **  The `C' queue file command operates on the next line,
+               **  so we use "gotctluser" to maintain state as follows:
+               **      0 - no controlling user,
+               **      1 - controlling user has been set but not used,
+               **      2 - controlling user must be used on next iteration.
+               */
+               if (gotctluser == 1)
+                       gotctluser++;
+               else if (gotctluser == 2)
+               {
+                       clrctluser();
+                       gotctluser = 0;
+               }
        }
 
        }
 
+       /* clear controlling user in case we break out prematurely */
+       clrctluser();
+
        FileName = NULL;
        FileName = NULL;
+
+       /*
+       **  If we haven't read any lines, this queue file is empty.
+       **  Arrange to remove it without referencing any null pointers.
+       */
+
+       if (LineNumber == 0)
+       {
+               errno = 0;
+               e->e_flags |= EF_CLRQUEUE | EF_FATALERRS | EF_RESPONSE;
+       }
+       return qfp;
 }
 \f/*
 **  PRINTQUEUE -- print out a representation of the mail queue
 }
 \f/*
 **  PRINTQUEUE -- print out a representation of the mail queue
@@ -679,12 +857,13 @@ printqueue()
        FILE *f;
        int nrequests;
        char buf[MAXLINE];
        FILE *f;
        int nrequests;
        char buf[MAXLINE];
+       char cbuf[MAXLINE];
 
        /*
        **  Read and order the queue.
        */
 
 
        /*
        **  Read and order the queue.
        */
 
-       nrequests = orderq();
+       nrequests = orderq(TRUE);
 
        /*
        **  Print the work list that we have read.
 
        /*
        **  Print the work list that we have read.
@@ -697,17 +876,22 @@ printqueue()
                return;
        }
 
                return;
        }
 
+       la = getla();   /* get load average */
+
        printf("\t\tMail Queue (%d request%s", nrequests, nrequests == 1 ? "" : "s");
        printf("\t\tMail Queue (%d request%s", nrequests, nrequests == 1 ? "" : "s");
-       if (nrequests > WLSIZE)
-               printf(", only %d printed", WLSIZE);
-       printf(")\n--QID-- --Size-- -----Q-Time----- ------------Sender/Recipient------------\n");
+       if (nrequests > QUEUESIZE)
+               printf(", only %d printed", QUEUESIZE);
+       if (Verbose)
+               printf(")\n--QID-- --Size-- -Priority- ---Q-Time--- -----------Sender/Recipient-----------\n");
+       else
+               printf(")\n--QID-- --Size-- -----Q-Time----- ------------Sender/Recipient------------\n");
        for (w = WorkQ; w != NULL; w = w->w_next)
        {
                struct stat st;
                auto time_t submittime = 0;
                long dfsize = -1;
        for (w = WorkQ; w != NULL; w = w->w_next)
        {
                struct stat st;
                auto time_t submittime = 0;
                long dfsize = -1;
-               char lf[20];
                char message[MAXLINE];
                char message[MAXLINE];
+               extern bool shouldqueue();
 
                f = fopen(w->w_name, "r");
                if (f == NULL)
 
                f = fopen(w->w_name, "r");
                if (f == NULL)
@@ -716,15 +900,16 @@ printqueue()
                        continue;
                }
                printf("%7s", w->w_name + 2);
                        continue;
                }
                printf("%7s", w->w_name + 2);
-               (void) strcpy(lf, w->w_name);
-               lf[0] = 'l';
-               if (stat(lf, &st) >= 0)
+               if (flock(fileno(f), LOCK_SH|LOCK_NB) < 0)
                        printf("*");
                        printf("*");
+               else if (shouldqueue(w->w_pri))
+                       printf("X");
                else
                        printf(" ");
                errno = 0;
 
                message[0] = '\0';
                else
                        printf(" ");
                errno = 0;
 
                message[0] = '\0';
+               cbuf[0] = '\0';
                while (fgets(buf, sizeof buf, f) != NULL)
                {
                        fixcrlf(buf, TRUE);
                while (fgets(buf, sizeof buf, f) != NULL)
                {
                        fixcrlf(buf, TRUE);
@@ -735,18 +920,41 @@ printqueue()
                                break;
 
                          case 'S':     /* sender name */
                                break;
 
                          case 'S':     /* sender name */
-                               printf("%8ld %.16s %.45s", dfsize,
-                                       ctime(&submittime), &buf[1]);
+                               if (Verbose)
+                                       printf("%8ld %10ld %.12s %.38s", dfsize,
+                                           w->w_pri, ctime(&submittime) + 4,
+                                           &buf[1]);
+                               else
+                                       printf("%8ld %.16s %.45s", dfsize,
+                                           ctime(&submittime), &buf[1]);
                                if (message[0] != '\0')
                                if (message[0] != '\0')
-                                       printf("\n\t\t\t\t  (%.43s)", message);
+                                       printf("\n\t\t (%.60s)", message);
+                               break;
+                         case 'C':     /* controlling user */
+                               if (strlen(buf) < MAXLINE-3)    /* sanity */
+                                       (void) strcat(buf, ") ");
+                               cbuf[0] = cbuf[1] = '(';
+                               (void) strncpy(&cbuf[2], &buf[1], MAXLINE-1);
+                               cbuf[MAXLINE-1] = '\0';
                                break;
 
                          case 'R':     /* recipient name */
                                break;
 
                          case 'R':     /* recipient name */
-                               printf("\n\t\t\t\t  %.45s", &buf[1]);
+                               if (cbuf[0] != '\0') {
+                                       /* prepend controlling user to `buf' */
+                                       (void) strncat(cbuf, &buf[1],
+                                                     MAXLINE-strlen(cbuf));
+                                       cbuf[MAXLINE-1] = '\0';
+                                       (void) strcpy(buf, cbuf);
+                                       cbuf[0] = '\0';
+                               }
+                               if (Verbose)
+                                       printf("\n\t\t\t\t\t %.38s", &buf[1]);
+                               else
+                                       printf("\n\t\t\t\t  %.45s", &buf[1]);
                                break;
 
                          case 'T':     /* creation time */
                                break;
 
                          case 'T':     /* creation time */
-                               (void) sscanf(&buf[1], "%ld", &submittime);
+                               submittime = atol(&buf[1]);
                                break;
 
                          case 'D':     /* data file name */
                                break;
 
                          case 'D':     /* data file name */
@@ -769,9 +977,6 @@ printqueue()
 **     Assigns an id code if one does not already exist.
 **     This code is very careful to avoid trashing existing files
 **     under any circumstances.
 **     Assigns an id code if one does not already exist.
 **     This code is very careful to avoid trashing existing files
 **     under any circumstances.
-**             We first create an nf file that is only used when
-**             assigning an id.  This file is always empty, so that
-**             we can never accidently truncate an lf file.
 **
 **     Parameters:
 **             e -- envelope to build it in/from.
 **
 **     Parameters:
 **             e -- envelope to build it in/from.
@@ -782,7 +987,7 @@ printqueue()
 **             a pointer to the new file name (in a static buffer).
 **
 **     Side Effects:
 **             a pointer to the new file name (in a static buffer).
 **
 **     Side Effects:
-**             Will create the lf and qf files if no id code is
+**             Will create the qf file if no id code is
 **             already assigned.  This will cause the envelope
 **             to be modified.
 */
 **             already assigned.  This will cause the envelope
 **             to be modified.
 */
@@ -800,8 +1005,6 @@ queuename(e, type)
        if (e->e_id == NULL)
        {
                char qf[20];
        if (e->e_id == NULL)
        {
                char qf[20];
-               char nf[20];
-               char lf[20];
 
                /* find a unique id */
                if (pid != getpid())
 
                /* find a unique id */
                if (pid != getpid())
@@ -812,10 +1015,6 @@ queuename(e, type)
                        c2 = 'A' - 1;
                }
                (void) sprintf(qf, "qfAA%05d", pid);
                        c2 = 'A' - 1;
                }
                (void) sprintf(qf, "qfAA%05d", pid);
-               (void) strcpy(lf, qf);
-               lf[0] = 'l';
-               (void) strcpy(nf, qf);
-               nf[0] = 'n';
 
                while (c1 < '~' || c2 < 'Z')
                {
 
                while (c1 < '~' || c2 < 'Z')
                {
@@ -826,35 +1025,22 @@ queuename(e, type)
                                c1++;
                                c2 = 'A' - 1;
                        }
                                c1++;
                                c2 = 'A' - 1;
                        }
-                       lf[2] = nf[2] = qf[2] = c1;
-                       lf[3] = nf[3] = qf[3] = ++c2;
-# ifdef DEBUG
+                       qf[2] = c1;
+                       qf[3] = ++c2;
                        if (tTd(7, 20))
                        if (tTd(7, 20))
-                               printf("queuename: trying \"%s\"\n", nf);
-# endif DEBUG
-
-# ifdef QUEUE
-                       if (access(lf, 0) >= 0 || access(qf, 0) >= 0)
-                               continue;
-                       errno = 0;
-                       i = creat(nf, FileMode);
-                       if (i < 0)
-                       {
-                               (void) unlink(nf);      /* kernel bug */
-                               continue;
-                       }
-                       (void) close(i);
-                       i = link(nf, lf);
-                       (void) unlink(nf);
-                       if (i < 0)
-                               continue;
-                       if (link(lf, qf) >= 0)
-                               break;
-                       (void) unlink(lf);
-# else QUEUE
-                       if (close(creat(qf, FileMode)) >= 0)
+                               printf("queuename: trying \"%s\"\n", qf);
+
+                       i = open(qf, O_WRONLY|O_CREAT|O_EXCL, FileMode);
+                       if (i < 0) {
+                               if (errno != EEXIST) {
+                                       syserr("queuename: Cannot create \"%s\" in \"%s\"",
+                                               qf, QueueDir);
+                                       exit(EX_UNAVAILABLE);
+                               }
+                       } else {
+                               (void) close(i);
                                break;
                                break;
-# endif QUEUE
+                       }
                }
                if (c1 >= '~' && c2 >= 'Z')
                {
                }
                if (c1 >= '~' && c2 >= 'Z')
                {
@@ -864,23 +1050,19 @@ queuename(e, type)
                }
                e->e_id = newstr(&qf[2]);
                define('i', e->e_id, e);
                }
                e->e_id = newstr(&qf[2]);
                define('i', e->e_id, e);
-# ifdef DEBUG
                if (tTd(7, 1))
                        printf("queuename: assigned id %s, env=%x\n", e->e_id, e);
 # ifdef LOG
                if (LogLevel > 16)
                        syslog(LOG_DEBUG, "%s: assigned id", e->e_id);
 # endif LOG
                if (tTd(7, 1))
                        printf("queuename: assigned id %s, env=%x\n", e->e_id, e);
 # ifdef LOG
                if (LogLevel > 16)
                        syslog(LOG_DEBUG, "%s: assigned id", e->e_id);
 # endif LOG
-# endif DEBUG
        }
 
        if (type == '\0')
                return (NULL);
        (void) sprintf(buf, "%cf%s", type, e->e_id);
        }
 
        if (type == '\0')
                return (NULL);
        (void) sprintf(buf, "%cf%s", type, e->e_id);
-# ifdef DEBUG
        if (tTd(7, 2))
                printf("queuename: %s\n", buf);
        if (tTd(7, 2))
                printf("queuename: %s\n", buf);
-# endif DEBUG
        return (buf);
 }
 \f/*
        return (buf);
 }
 \f/*
@@ -900,17 +1082,154 @@ unlockqueue(e)
        ENVELOPE *e;
 {
        /* remove the transcript */
        ENVELOPE *e;
 {
        /* remove the transcript */
-#ifdef DEBUG
 # ifdef LOG
        if (LogLevel > 19)
                syslog(LOG_DEBUG, "%s: unlock", e->e_id);
 # endif LOG
        if (!tTd(51, 4))
 # ifdef LOG
        if (LogLevel > 19)
                syslog(LOG_DEBUG, "%s: unlock", e->e_id);
 # endif LOG
        if (!tTd(51, 4))
-#endif DEBUG
                xunlink(queuename(e, 'x'));
 
                xunlink(queuename(e, 'x'));
 
-# ifdef QUEUE
-       /* last but not least, remove the lock */
-       xunlink(queuename(e, 'l'));
-# endif QUEUE
+}
+\f/*
+**  GETCTLUSER -- return controlling user if mailing to prog or file
+**
+**     Check for a "|" or "/" at the beginning of the address.  If
+**     found, return a controlling username.
+**
+**     Parameters:
+**             a - the address to check out
+**
+**     Returns:
+**             Either NULL, if we werent mailing to a program or file,
+**             or a controlling user name (possibly in getpwuid's
+**             static buffer).
+**
+**     Side Effects:
+**             none.
+*/
+
+char *
+getctluser(a)
+       ADDRESS *a;
+{
+       extern ADDRESS *getctladdr();
+       struct passwd *pw;
+       char *retstr;
+
+       /*
+       **  Get unquoted user for file, program or user.name check.
+       **  N.B. remove this code block to always emit controlling
+       **  addresses (at the expense of backward compatibility).
+       */
+
+       {
+               char buf[MAXNAME];
+               (void) strncpy(buf, a->q_paddr, MAXNAME);
+               buf[MAXNAME-1] = '\0';
+               stripquotes(buf, TRUE);
+
+               if (buf[0] != '|' && buf[0] != '/')
+                       return((char *)NULL);
+       }
+
+       a = getctladdr(a);              /* find controlling address */
+
+       if (a != NULL && a->q_uid != 0 && (pw = getpwuid(a->q_uid)) != NULL)
+               retstr = pw->pw_name;
+       else                            /* use default user */
+               retstr = DefUser;
+
+       if (tTd(40, 5))
+               printf("Set controlling user for `%s' to `%s'\n",
+                      (a == NULL)? "<null>": a->q_paddr, retstr);
+
+       return(retstr);
+}
+\f/*
+**  SETCTLUSER - sets `CtlUser' to controlling user
+**  CLRCTLUSER - clears controlling user (no params, nothing returned)
+**
+**     These routines manipulate `CtlUser'.
+**
+**     Parameters:
+**             str  - controlling user as passed to setctluser()
+**
+**     Returns:
+**             None.
+**
+**     Side Effects:
+**             `CtlUser' is changed.
+*/
+
+static char CtlUser[MAXNAME];
+
+setctluser(str)
+register char *str;
+{
+       (void) strncpy(CtlUser, str, MAXNAME);
+       CtlUser[MAXNAME-1] = '\0';
+}
+
+clrctluser()
+{
+       CtlUser[0] = '\0';
+}
+
+\f/*
+**  SETCTLADDR -- create a controlling address
+**
+**     If global variable `CtlUser' is set and we are given a valid
+**     address, make that address a controlling address; change the
+**     `q_uid', `q_gid', and `q_ruser' fields and set QGOODUID.
+**
+**     Parameters:
+**             a - address for which control uid/gid info may apply
+**
+**     Returns:
+**             None.   
+**
+**     Side Effects:
+**             Fills in uid/gid fields in address and sets QGOODUID
+**             flag if appropriate.
+*/
+
+setctladdr(a)
+       ADDRESS *a;
+{
+       struct passwd *pw;
+
+       /*
+       **  If there is no current controlling user, or we were passed a
+       **  NULL addr ptr or we already have a controlling user, return.
+       */
+
+       if (CtlUser[0] == '\0' || a == NULL || a->q_ruser)
+               return;
+
+       /*
+       **  Set up addr fields for controlling user.  If `CtlUser' is no
+       **  longer valid, use the default user/group.
+       */
+
+       if ((pw = getpwnam(CtlUser)) != NULL)
+       {
+               if (a->q_home)
+                       free(a->q_home);
+               a->q_home = newstr(pw->pw_dir);
+               a->q_uid = pw->pw_uid;
+               a->q_gid = pw->pw_gid;
+               a->q_ruser = newstr(CtlUser);
+       }
+       else
+       {
+               a->q_uid = DefUid;
+               a->q_gid = DefGid;
+               a->q_ruser = newstr(DefUser);
+       }
+
+       a->q_flags |= QGOODUID;         /* flag as a "ctladdr"  */
+
+       if (tTd(40, 5))
+               printf("Restored controlling user for `%s' to `%s'\n",
+                      a->q_paddr, a->q_ruser);
 }
 }