BSD 4_3_Net_2 release
[unix-history] / usr / src / usr.sbin / sendmail / src / queue.c
index cbb3864..03b226a 100644 (file)
+/*
+ * 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/stat.h>
-# include <ndir.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
-SCCSID(@(#)queue.c     3.26            %G%     (no queueing));
-# else QUEUE
-
-SCCSID(@(#)queue.c     3.26            %G%);
+# ifdef QUEUE
 
 /*
 
 /*
+**  Work queue.
+*/
+
+struct work
+{
+       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;
+extern int la;
+
+WORK   *WorkQ;                 /* queue of things to be done */
+\f/*
 **  QUEUEUP -- queue a message up for future transmission.
 **
 **  QUEUEUP -- queue a message up for future transmission.
 **
-**     The queued message should already be in the correct place.
-**     This routine just outputs the control file as appropriate.
-**
 **     Parameters:
 **             e -- the envelope to queue up.
 **             queueall -- if TRUE, queue all addresses, rather than
 **                     just those with the QQUEUEUP flag set.
 **     Parameters:
 **             e -- the envelope to queue up.
 **             queueall -- if TRUE, queue all addresses, rather than
 **                     just those with the QQUEUEUP flag set.
+**             announce -- if TRUE, tell when you are queueing up.
 **
 **     Returns:
 **
 **     Returns:
-**             none.
+**             locked FILE* to q file
 **
 **     Side Effects:
 **
 **     Side Effects:
-**             The current request (only unsatisfied addresses)
-**                     are saved in a control file.
+**             The current request are saved in a control file.
 */
 
 */
 
-queueup(e, queueall)
+FILE *
+queueup(e, queueall, announce)
        register ENVELOPE *e;
        bool queueall;
        register ENVELOPE *e;
        bool queueall;
+       bool announce;
 {
 {
-       char cf[MAXNAME];
-       char buf[MAXNAME];
-       register FILE *cfp;
+       char *qf;
+       char buf[MAXLINE], tf[MAXLINE];
+       register FILE *tfp;
        register HDR *h;
        register ADDRESS *q;
        register HDR *h;
        register ADDRESS *q;
-       extern char *mktemp();
-       register int i;
+       MAILER nullmailer;
+       int fd, ret;
 
        /*
        **  Create control file.
        */
 
 
        /*
        **  Create control file.
        */
 
-       (void) strcpy(cf, QueueDir);
-       (void) strcat(cf, "/tfXXXXXX");
-       (void) mktemp(cf);
-       cfp = fopen(cf, "w");
-       if (cfp == NULL)
-       {
-               syserr("queueup: cannot create control file %s", cf);
-               return;
-       }
-       (void) chmod(cf, 0600);
+       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 (Debug)
-               printf("queueing in %s\n", cf);
-# endif DEBUG
+       if (tTd(40, 1))
+               printf("queueing %s\n", e->e_id);
 
        /*
        **  If there is no data file yet, create one.
 
        /*
        **  If there is no data file yet, create one.
@@ -68,90 +132,157 @@ queueup(e, queueall)
        if (e->e_df == NULL)
        {
                register FILE *dfp;
        if (e->e_df == NULL)
        {
                register FILE *dfp;
+               extern putbody();
 
 
-               (void) strcpy(buf, QueueDir);
-               (void) strcat(buf, "/dfXXXXXX");
-               e->e_df = newstr(mktemp(buf));
-               dfp = fopen(e->e_df, "w");
-               if (dfp == NULL)
+               e->e_df = newstr(queuename(e, 'd'));
+               fd = open(e->e_df, O_WRONLY|O_CREAT, FileMode);
+               if (fd < 0)
                {
                        syserr("queueup: cannot create %s", e->e_df);
                {
                        syserr("queueup: cannot create %s", e->e_df);
-                       (void) fclose(cfp);
-                       return;
+                       (void) fclose(tfp);
+                       return NULL;
                }
                }
-               (void) chmod(e->e_df, 0600);
-               (*e->e_putbody)(dfp, Mailer[1], FALSE);
+               dfp = fdopen(fd, "w");
+               (*e->e_putbody)(dfp, ProgMailer, e);
                (void) fclose(dfp);
                (void) fclose(dfp);
+               e->e_putbody = putbody;
        }
 
        /*
        **  Output future work requests.
        }
 
        /*
        **  Output future work requests.
+       **      Priority and creation time should be first, since
+       **      they are required by orderq.
        */
 
        */
 
-       /* output name of data file */
-       fprintf(cfp, "D%s\n", e->e_df);
+       /* output message priority */
+       fprintf(tfp, "P%ld\n", e->e_msgpriority);
 
 
-       /* output name of sender */
-       fprintf(cfp, "S%s\n", e->e_from.q_paddr);
+       /* output creation time */
+       fprintf(tfp, "T%ld\n", e->e_ctime);
 
 
-       /* output timeout */
-       fprintf(cfp, "T%ld\n", TimeOut);
+       /* output name of data file */
+       fprintf(tfp, "D%s\n", e->e_df);
 
 
-       /* output message priority */
-       fprintf(cfp, "P%ld\n", e->e_msgpriority);
+       /* message from envelope, if it exists */
+       if (e->e_message != NULL)
+               fprintf(tfp, "M%s\n", e->e_message);
 
 
-       /* output message class */
-       fprintf(cfp, "C%d\n", e->e_class);
+       /* output name of sender */
+       fprintf(tfp, "S%s\n", e->e_from.q_paddr);
 
 
-       /* output macro definitions */
-       for (i = 0; i < 128; i++)
+       /* output list of recipient addresses */
+       for (q = e->e_sendqueue; q != NULL; q = q->q_next)
        {
        {
-               register char *p = e->e_macro[i];
+               if (queueall ? !bitset(QDONTSEND|QSENT, q->q_flags) :
+                              bitset(QQUEUEUP, q->q_flags))
+               {
+                       char *ctluser, *getctluser();
 
 
-               if (p != NULL && i != (int) 'b')
-                       fprintf(cfp, "M%c%s\n", i, p);
+                       if ((ctluser = getctluser(q)) != NULL)
+                               fprintf(tfp, "C%s\n", ctluser);
+                       fprintf(tfp, "R%s\n", q->q_paddr);
+                       if (announce)
+                       {
+                               e->e_to = q->q_paddr;
+                               message(Arpa_Info, "queued");
+                               if (LogLevel > 4)
+                                       logdelivery("queued");
+                               e->e_to = NULL;
+                       }
+                       if (tTd(40, 1))
+                       {
+                               printf("queueing ");
+                               printaddr(q, FALSE);
+                       }
+               }
        }
 
        }
 
-       /* output list of recipient addresses */
-       for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+       /* output list of error recipients */
+       for (q = e->e_errorqueue; q != NULL; q = q->q_next)
        {
        {
-# ifdef DEBUG
-               if (Debug > 0)
+               if (!bitset(QDONTSEND, q->q_flags))
                {
                {
-                       printf("queueing ");
-                       printaddr(q, FALSE);
+                       char *ctluser, *getctluser();
+
+                       if ((ctluser = getctluser(q)) != NULL)
+                               fprintf(tfp, "C%s\n", ctluser);
+                       fprintf(tfp, "E%s\n", q->q_paddr);
                }
                }
-# endif DEBUG
-               if (queueall || bitset(QQUEUEUP, q->q_flags))
-                       fprintf(cfp, "R%s\n", q->q_paddr);
        }
 
        }
 
-       /* output headers for this message */
+       /*
+       **  Output headers for this message.
+       **      Expand macros completely here.  Queue run will deal with
+       **      everything as absolute headers.
+       **              All headers that must be relative to the recipient
+       **              can be cracked later.
+       **      We set up a "null mailer" -- i.e., a mailer that will have
+       **      no effect on the addresses as they are output.
+       */
+
+       bzero((char *) &nullmailer, sizeof nullmailer);
+       nullmailer.m_r_rwset = nullmailer.m_s_rwset = -1;
+       nullmailer.m_eol = "\n";
+
+       define('g', "\001f", e);
        for (h = e->e_header; h != NULL; h = h->h_link)
        {
        for (h = e->e_header; h != NULL; h = h->h_link)
        {
+               extern bool bitzerop();
+
+               /* don't output null headers */
                if (h->h_value == NULL || h->h_value[0] == '\0')
                        continue;
                if (h->h_value == NULL || h->h_value[0] == '\0')
                        continue;
-               fprintf(cfp, "H");
-               if (h->h_mflags != 0 && bitset(H_CHECK|H_ACHECK, h->h_flags))
-                       mfdecode(h->h_mflags, cfp);
-               fprintf(cfp, "%s: %s\n", h->h_field, h->h_value);
+
+               /* don't output resent headers on non-resent messages */
+               if (bitset(H_RESENT, h->h_flags) && !bitset(EF_RESENT, e->e_flags))
+                       continue;
+
+               /* output this header */
+               fprintf(tfp, "H");
+
+               /* if conditional, output the set of conditions */
+               if (!bitzerop(h->h_mflags) && bitset(H_CHECK|H_ACHECK, h->h_flags))
+               {
+                       int j;
+
+                       (void) putc('?', tfp);
+                       for (j = '\0'; j <= '\177'; j++)
+                               if (bitnset(j, h->h_mflags))
+                                       (void) putc(j, tfp);
+                       (void) putc('?', tfp);
+               }
+
+               /* output the header: expand macros, convert addresses */
+               if (bitset(H_DEFAULT, h->h_flags))
+               {
+                       (void) expand(h->h_value, buf, &buf[sizeof buf], e);
+                       fprintf(tfp, "%s: %s\n", h->h_field, buf);
+               }
+               else if (bitset(H_FROM|H_RCPT, h->h_flags))
+               {
+                       commaize(h, h->h_value, tfp, bitset(EF_OLDSTYLE, e->e_flags),
+                                &nullmailer);
+               }
+               else
+                       fprintf(tfp, "%s: %s\n", h->h_field, h->h_value);
        }
 
        /*
        **  Clean up.
        */
 
        }
 
        /*
        **  Clean up.
        */
 
-       (void) fclose(cfp);
-       (void) strcpy(buf, QueueDir);
-       (void) strcat(buf, "/cfXXXXXX");
-       (void) mktemp(buf);
-       if (link(cf, buf) < 0)
-               syserr("cannot link(%s, %s), df=%s", cf, buf, e->e_df);
-       else
-               (void) unlink(cf);
+       qf = queuename(e, 'q');
+       if (rename(tf, qf) < 0)
+               syserr("cannot rename(%s, %s), df=%s", tf, qf, e->e_df);
+       errno = 0;
 
 
-       /* disconnect this temp file from the job */
-       e->e_df = NULL;
+# 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.
@@ -160,7 +291,9 @@ queueup(e, queueall)
 **     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.
@@ -169,13 +302,27 @@ queueup(e, queueall)
 **             runs things in the mail queue.
 */
 
 **             runs things in the mail queue.
 */
 
-bool   ReorderQueue;           /* if set, reorder the send queue */
-int    QueuePid;               /* pid of child running queue */
-
 runqueue(forkflag)
        bool forkflag;
 {
 runqueue(forkflag)
        bool forkflag;
 {
-       extern reordersig();
+       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.
@@ -183,141 +330,107 @@ runqueue(forkflag)
 
        if (forkflag)
        {
 
        if (forkflag)
        {
-               QueuePid = dofork();
-               if (QueuePid > 0)
+               int pid;
+
+               pid = dofork();
+               if (pid != 0)
                {
                {
-                       /* parent */
+                       extern void reapchild();
+
+                       /* parent -- pick up intermediate zombie */
+#ifndef SIGCHLD
+                       (void) waitfor(pid);
+#else SIGCHLD
+                       (void) signal(SIGCHLD, reapchild);
+#endif SIGCHLD
+                       if (QueueIntvl != 0)
+                               (void) setevent(QueueIntvl, runqueue, TRUE);
                        return;
                }
                        return;
                }
-               else
-                       (void) alarm(0);
+               /* child -- double fork */
+#ifndef SIGCHLD
+               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());
+# endif LOG
+
        /*
        /*
-       **  Arrange to reorder the queue at polite intervals.
+       **  Release any resources used by the daemon code.
        */
 
        */
 
-       if (QueueIntvl != 0)
-       {
-               (void) signal(SIGALRM, reordersig);
-               (void) alarm(QueueIntvl);
-       }
+# ifdef 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.
        **      Then, process the work in that order.
        **              But if you take too long, start over.
 
        /*
        **  Start making passes through the queue.
        **      First, read and sort the entire queue.
        **      Then, process the work in that order.
        **              But if you take too long, start over.
-       **      There is a race condition at the end -- we could get
-       **              a reorder signal after finishing the queue.
-       **              In this case we will hang for one more queue
-       **              interval -- clearly a botch, but rare and
-       **              relatively innocuous.
        */
 
        */
 
-       for (;;)
-       {
-               /* order the existing work requests */
-               orderq();
-               ReorderQueue = FALSE;
-
-               /* process them once at a time */
-               while (WorkQ != NULL)
-               {
-                       WORK *w = WorkQ;
-
-                       WorkQ = WorkQ->w_next;
-                       dowork(w);
-                       free(w->w_name);
-                       free((char *) w);
-                       if (ReorderQueue)
-                               break;
-               }
-
-               /* if we are just doing one pass, then we are done */
-               if (QueueIntvl == 0)
-                       break;
-
-               /* wait for work -- note (harmless) race condition here */
-               if (!ReorderQueue)
-                       pause();
-       }
+       /* order the existing work requests */
+       (void) orderq(FALSE);
 
 
-       /* no work to do -- just exit */
-       finis();
-}
-\f/*
-**  REORDERSIG -- catch the alarm signal and tell sendmail to reorder queue.
-**
-**     Parameters:
-**             none.
-**
-**     Returns:
-**             none.
-**
-**     Side Effects:
-**             sets the "reorder work queue" flag.
-*/
-
-reordersig()
-{
-       if (QueuePid == 0)
-       {
-               /* we are in a child doing queueing */
-               ReorderQueue = TRUE;
-       }
-       else
+       /* process them once at a time */
+       while (WorkQ != NULL)
        {
        {
-               /* we are in a parent -- poke child or start new one */
-               if (kill(QueuePid, SIGALRM) < 0)
-               {
-                       /* no child -- get zombie & start new one */
-                       static int st;
+               WORK *w = WorkQ;
 
 
-                       (void) wait(&st);
-                       QueuePid = dofork();
-                       if (QueuePid == 0)
-                       {
-                               /* new child; run queue */
-                               runqueue(FALSE);
-                               finis();
-                       }
-               }
+               WorkQ = WorkQ->w_next;
+               dowork(w);
+               free(w->w_name);
+               free((char *) w);
        }
 
        }
 
-       /*
-       **  Arrange to get this signal again.
-       */
-
-       (void) signal(SIGALRM, reordersig);
-       (void) alarm(QueueIntvl);
+       /* 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:
 **
 **     Returns:
-**             none.
+**             The number of request in the queue (not necessarily
+**             the number of requests in WorkQ however).
 **
 **     Side Effects:
 **             Sets WorkQ to the queue of available work, in order.
 */
 
 **
 **     Side Effects:
 **             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
 
 
-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];
-       int wn = 0;
+       WORK wlist[QUEUESIZE+1];
+       int wn = -1;
        extern workcmpf();
        extern workcmpf();
-       extern char *QueueDir;
 
        /* clear out old WorkQ */
        for (w = WorkQ; w != NULL; )
 
        /* clear out old WorkQ */
        for (w = WorkQ; w != NULL; )
@@ -331,93 +444,110 @@ orderq()
        }
 
        /* open the queue directory */
        }
 
        /* open the queue directory */
-       f = opendir(QueueDir);
+       f = opendir(".");
        if (f == NULL)
        {
        if (f == NULL)
        {
-               syserr("orderq: cannot open %s", QueueDir);
-               return;
+               syserr("orderq: cannot open \"%s\" as \".\"", QueueDir);
+               return (0);
        }
 
        /*
        **  Read the work directory.
        */
 
        }
 
        /*
        **  Read the work directory.
        */
 
-       while (wn < WLSIZE && (d = readdir(f)) != NULL)
+       while ((d = readdir(f)) != NULL)
        {
        {
-               char cbuf[MAXNAME];
-               char lbuf[MAXNAME];
                FILE *cf;
                FILE *cf;
-               register char *p;
+               char lbuf[MAXNAME];
 
                /* is this an interesting entry? */
 
                /* is this an interesting entry? */
-               if (d->d_name[0] != 'c')
+               if (d->d_name[0] != 'q' || d->d_name[1] != 'f')
                        continue;
 
                        continue;
 
-               /* yes -- find the control file location */
-               (void) strcpy(cbuf, QueueDir);
-               (void) strcat(cbuf, "/");
-               p = &cbuf[strlen(cbuf)];
-               (void) strcpy(p, d->d_name);
-
-               /* open control file */
-               cf = fopen(cbuf, "r");
+               /* yes -- open control file (if not too many files) */
+               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); */
                if (cf == NULL)
                {
                        /* this may be some random person sending hir msgs */
                        /* syserr("orderq: cannot open %s", cbuf); */
+                       if (tTd(41, 2))
+                               printf("orderq: cannot open %s (%d)\n",
+                                       d->d_name, errno);
                        errno = 0;
                        errno = 0;
+                       wn--;
                        continue;
                }
                        continue;
                }
-               wlist[wn].w_name = newstr(cbuf);
+               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)
                {
                {
-                       fixcrlf(lbuf, TRUE);
+                       extern long atol();
 
                        switch (lbuf[0])
                        {
 
                        switch (lbuf[0])
                        {
-                         case 'P':             /* message priority */
-                               (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;
                        }
                }
                                break;
                        }
                }
-               wn++;
                (void) fclose(cf);
                (void) fclose(cf);
+
+               if (!doall && shouldqueue(w->w_pri))
+               {
+                       /* don't even bother sorting this job in */
+                       wn--;
+               }
        }
        (void) closedir(f);
        }
        (void) closedir(f);
+       wn++;
 
        /*
        **  Sort the work directory.
        */
 
 
        /*
        **  Sort the work directory.
        */
 
-       qsort(wlist, wn, sizeof *wlist, workcmpf);
+       qsort((char *) wlist, min(wn, QUEUESIZE), sizeof *wlist, workcmpf);
 
        /*
        **  Convert the work list into canonical form.
 
        /*
        **  Convert the work list into canonical form.
+       **      Should be turning it into a list of envelopes here perhaps.
        */
 
        */
 
-       wp = &WorkQ;
-       for (i = 0; i < wn; i++)
+       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 (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);
        }
        {
                for (w = WorkQ; w != NULL; w = w->w_next)
                        printf("%32s: pri=%ld\n", w->w_name, w->w_pri);
        }
-# endif DEBUG
+
+       return (wn);
 }
 \f/*
 }
 \f/*
-**     WORKCMPF -- compare function for ordering work.
+**  WORKCMPF -- compare function for ordering work.
 **
 **     Parameters:
 **             a -- the first argument.
 **
 **     Parameters:
 **             a -- the first argument.
@@ -425,22 +555,23 @@ orderq()
 **
 **     Returns:
 **             -1 if a < b
 **
 **     Returns:
 **             -1 if a < b
-**             0 if a == b
-**             1 if a > b
+**              0 if a == b
+**             +1 if a > b
 **
 **     Side Effects:
 **             none.
 */
 
 **
 **     Side Effects:
 **             none.
 */
 
-# define PRIFACT       1800            /* bytes each priority point is worth */
-
 workcmpf(a, b)
        register WORK *a;
        register WORK *b;
 {
 workcmpf(a, 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)
+       else if (pa > pb)
                return (1);
        else
                return (-1);
                return (1);
        else
                return (-1);
@@ -462,146 +593,187 @@ dowork(w)
        register WORK *w;
 {
        register int i;
        register WORK *w;
 {
        register int i;
-       auto int xstat;
+       extern bool shouldqueue();
 
 
-# ifdef DEBUG
-       if (Debug)
+       if (tTd(40, 1))
                printf("dowork: %s pri %ld\n", w->w_name, w->w_pri);
                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;
        }
 
-       if (i == 0)
+       /*
+       **  Fork for work.
+       */
+
+       if (ForkQueueRuns)
        {
        {
-               char buf[MAXNAME];
+               i = fork();
+               if (i < 0)
+               {
+                       syserr("dowork: cannot fork");
+                       return;
+               }
+       }
+       else
+       {
+               i = 0;
+       }
 
 
+       if (i == 0)
+       {
+               FILE *qflock, *readqf();
                /*
                **  CHILD
                /*
                **  CHILD
-               **      Change the name of the control file to avoid
-               **              duplicate deliveries.   Then run the file
-               **              as though we had just read it.
+               **      Lock the control file to avoid duplicate deliveries.
+               **              Then run the file as though we had just read it.
                **      We save an idea of the temporary name so we
                **              can recover on interrupt.
                */
 
                **      We save an idea of the temporary name so we
                **              can recover on interrupt.
                */
 
+               /* set basic modes, etc. */
                (void) alarm(0);
                (void) alarm(0);
-               FatalErrors = FALSE;
+               clearenvelope(CurEnv, FALSE);
                QueueRun = TRUE;
                QueueRun = TRUE;
-               MailBack = TRUE;
-               (void) strcpy(buf, QueueDir);
-               (void) strcat(buf, "/tfXXXXXX");
-               (void) mktemp(buf);
-               if (link(w->w_name, buf) < 0)
-               {
-                       syserr("dowork: link(%s, %s)", w->w_name, buf);
+               ErrorMode = EM_MAIL;
+               CurEnv->e_id = &w->w_name[2];
+# ifdef LOG
+               if (LogLevel > 11)
+                       syslog(LOG_DEBUG, "%s: dowork, pid=%d", CurEnv->e_id,
+                              getpid());
+# endif LOG
 
 
-                       /* it's ok to lie -- it will be run later */
-                       exit(EX_OK);
-               }
-               ControlFile = newstr(buf);
-               (void) unlink(w->w_name);
-
-               /* create ourselves a transcript file */
-               openxscrpt();
-
-               /* do basic system initialization */
-               initsys();
+               /* don't use the headers from sendmail.cf... */
+               CurEnv->e_header = NULL;
 
                /* read the queue control file */
 
                /* read the queue control file */
-               readqf(buf);
-
-               /* do the delivery */
-               if (!FatalErrors)
-                       sendall(CurEnv, FALSE);
+               /*  and lock the control file during processing */
+               if ((qflock=readqf(CurEnv, TRUE)) == NULL)
+               {
+                       if (ForkQueueRuns)
+                               exit(EX_OK);
+                       else
+                               return;
+               }
 
 
-               /* if still not sent, perhaps we should time out.... */
-# ifdef DEBUG
-               if (Debug > 2)
-                       printf("CurTime=%ld, TimeOut=%ld\n", CurTime, TimeOut);
-# endif DEBUG
-               if (CurEnv->e_queueup && CurTime > TimeOut)
-                       timeout(w);
+               CurEnv->e_flags |= EF_INQUEUE;
+               eatheader(CurEnv);
 
 
-               /* get rid of the temporary file -- a new cf will be made */
-               ControlFile = NULL;
-               (void) unlink(buf);
+               /* do the delivery */
+               if (!bitset(EF_FATALERRS, CurEnv->e_flags))
+                       sendall(CurEnv, SM_DELIVER);
 
                /* finish up and exit */
 
                /* finish up and exit */
-               finis();
+               if (ForkQueueRuns)
+                       finis();
+               else
+                       dropenvelope(CurEnv);
+               fclose(qflock);
        }
        }
-
-       /*
-       **  Parent -- pick up results.
-       */
-
-       errno = 0;
-       while ((i = wait(&xstat)) > 0 && errno != EINTR)
+       else
        {
        {
-               if (errno == EINTR)
-               {
-                       errno = 0;
-               }
+               /*
+               **  Parent -- pick up results.
+               */
+
+               errno = 0;
+               (void) waitfor(i);
        }
 }
 \f/*
 **  READQF -- read queue file and set up environment.
 **
 **     Parameters:
        }
 }
 \f/*
 **  READQF -- read queue file and set up environment.
 **
 **     Parameters:
-**             cf -- name of queue control file.
+**             e -- the envelope of the job to run.
+**             full -- if set, read in all information.  Otherwise just
+**                     read in info needed for a queue print.
 **
 **     Returns:
 **
 **     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.
 */
 
-readqf(cf)
-       char *cf;
+FILE *
+readqf(e, full)
+       register ENVELOPE *e;
+       bool full;
 {
 {
-       register FILE *f;
-       char buf[MAXLINE];
-       extern ADDRESS *sendto();
+       char *qf;
+       register FILE *qfp;
+       char buf[MAXFIELD];
+       extern char *fgetfolded();
+       extern long atol();
+       int gotctluser = 0;
+       int fd;
 
        /*
 
        /*
-       **  Open the file created by queueup.
+       **  Read and process the file.
        */
 
        */
 
-       f = fopen(cf, "r");
-       if (f == NULL)
+       qf = queuename(e, 'q');
+       qfp = fopen(qf, "r");
+       if (qfp == NULL)
        {
        {
-               syserr("readqf: no cf file %s", cf);
-               return;
+               if (errno != ENOENT)
+                       syserr("readqf: no control file %s", qf);
+               return NULL;
        }
 
        }
 
-       /*
-       **  Read and process the file.
-       */
-
-       if (Verbose)
-               printf("\nRunning %s\n", cf);
-       while (fgets(buf, sizeof buf, f) != NULL)
+       if (flock(fileno(qfp), LOCK_EX|LOCK_NB) < 0)
        {
        {
-               fixcrlf(buf, TRUE);
+# 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)
+       {
+               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 */
-                       (void) chompheader(&buf[1], FALSE);
+                       if (full)
+                               (void) chompheader(&buf[1], FALSE);
+                       break;
+
+                 case 'M':             /* message */
+                       e->e_message = newstr(&buf[1]);
                        break;
 
                  case 'S':             /* sender */
                        break;
 
                  case 'S':             /* sender */
@@ -609,69 +781,455 @@ readqf(cf)
                        break;
 
                  case 'D':             /* data file name */
                        break;
 
                  case 'D':             /* data file name */
-                       CurEnv->e_df = newstr(&buf[1]);
-                       TempFile = fopen(CurEnv->e_df, "r");
-                       if (TempFile == NULL)
-                               syserr("readqf: cannot open %s", CurEnv->e_df);
+                       if (!full)
+                               break;
+                       e->e_df = newstr(&buf[1]);
+                       e->e_dfp = fopen(e->e_df, "r");
+                       if (e->e_dfp == NULL)
+                               syserr("readqf: cannot open %s", e->e_df);
                        break;
 
                        break;
 
-                 case 'T':             /* timeout */
-                       (void) sscanf(&buf[1], "%ld", &TimeOut);
+                 case 'T':             /* init time */
+                       e->e_ctime = atol(&buf[1]);
                        break;
 
                  case 'P':             /* message priority */
                        break;
 
                  case 'P':             /* message priority */
-                       (void) sscanf(&buf[1], "%ld", &CurEnv->e_msgpriority);
-
-                       /* make sure that big things get sent eventually */
-                       CurEnv->e_msgpriority -= WKTIMEFACT;
-                       break;
-
-                 case 'C':             /* message class */
-                       (void) sscanf(&buf[1], "%hd", &CurEnv->e_class);
+                       e->e_msgpriority = atol(&buf[1]) + WkTimeFact;
                        break;
 
                        break;
 
-                 case 'M':             /* define macro */
-                       define(buf[1], newstr(&buf[2]));
+                 case '\0':            /* blank line; ignore */
                        break;
 
                  default:
                        break;
 
                  default:
-                       syserr("readqf(%s): bad line \"%s\"", cf, 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;
+
+       /*
+       **  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/*
 }
 \f/*
-**  TIMEOUT -- process timeout on queue file.
+**  PRINTQUEUE -- print out a representation of the mail queue
 **
 **     Parameters:
 **
 **     Parameters:
-**             w -- pointer to work request that timed out.
+**             none.
 **
 **     Returns:
 **             none.
 **
 **     Side Effects:
 **
 **     Returns:
 **             none.
 **
 **     Side Effects:
-**             Returns a message to the sender saying that this
-**             message has timed out.
+**             Prints a listing of the mail queue on the standard output.
 */
 
 */
 
-timeout(w)
-       register WORK *w;
+printqueue()
 {
 {
+       register WORK *w;
+       FILE *f;
+       int nrequests;
        char buf[MAXLINE];
        char buf[MAXLINE];
-       extern char *TextTimeOut;
+       char cbuf[MAXLINE];
+
+       /*
+       **  Read and order the queue.
+       */
+
+       nrequests = orderq(TRUE);
+
+       /*
+       **  Print the work list that we have read.
+       */
+
+       /* first see if there is anything */
+       if (nrequests <= 0)
+       {
+               printf("Mail queue is empty\n");
+               return;
+       }
+
+       la = getla();   /* get load average */
 
 
-# ifdef DEBUG
-       if (Debug > 0)
-               printf("timeout(%s)\n", w->w_name);
-# endif DEBUG
-       message(Arpa_Info, "Message has timed out");
+       printf("\t\tMail Queue (%d request%s", nrequests, nrequests == 1 ? "" : "s");
+       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;
+               char message[MAXLINE];
+               extern bool shouldqueue();
+
+               f = fopen(w->w_name, "r");
+               if (f == NULL)
+               {
+                       errno = 0;
+                       continue;
+               }
+               printf("%7s", w->w_name + 2);
+               if (flock(fileno(f), LOCK_SH|LOCK_NB) < 0)
+                       printf("*");
+               else if (shouldqueue(w->w_pri))
+                       printf("X");
+               else
+                       printf(" ");
+               errno = 0;
 
 
-       /* return message to sender */
-       (void) sprintf(buf, "Cannot send mail for %s", TextTimeOut);
-       (void) returntosender(buf, &CurEnv->e_from, TRUE);
+               message[0] = '\0';
+               cbuf[0] = '\0';
+               while (fgets(buf, sizeof buf, f) != NULL)
+               {
+                       fixcrlf(buf, TRUE);
+                       switch (buf[0])
+                       {
+                         case 'M':     /* error message */
+                               (void) strcpy(message, &buf[1]);
+                               break;
 
 
-       /* arrange to remove files from queue */
-       CurEnv->e_queueup = FALSE;
+                         case 'S':     /* sender name */
+                               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')
+                                       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 */
+                               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 */
+                               submittime = atol(&buf[1]);
+                               break;
+
+                         case 'D':     /* data file name */
+                               if (stat(&buf[1], &st) >= 0)
+                                       dfsize = st.st_size;
+                               break;
+                       }
+               }
+               if (submittime == (time_t) 0)
+                       printf(" (no control file)");
+               printf("\n");
+               (void) fclose(f);
+       }
 }
 
 # endif QUEUE
 }
 
 # endif QUEUE
+\f/*
+**  QUEUENAME -- build a file name in the queue directory for this envelope.
+**
+**     Assigns an id code if one does not already exist.
+**     This code is very careful to avoid trashing existing files
+**     under any circumstances.
+**
+**     Parameters:
+**             e -- envelope to build it in/from.
+**             type -- the file type, used as the first character
+**                     of the file name.
+**
+**     Returns:
+**             a pointer to the new file name (in a static buffer).
+**
+**     Side Effects:
+**             Will create the qf file if no id code is
+**             already assigned.  This will cause the envelope
+**             to be modified.
+*/
+
+char *
+queuename(e, type)
+       register ENVELOPE *e;
+       char type;
+{
+       static char buf[MAXNAME];
+       static int pid = -1;
+       char c1 = 'A';
+       char c2 = 'A';
+
+       if (e->e_id == NULL)
+       {
+               char qf[20];
+
+               /* find a unique id */
+               if (pid != getpid())
+               {
+                       /* new process -- start back at "AA" */
+                       pid = getpid();
+                       c1 = 'A';
+                       c2 = 'A' - 1;
+               }
+               (void) sprintf(qf, "qfAA%05d", pid);
+
+               while (c1 < '~' || c2 < 'Z')
+               {
+                       int i;
+
+                       if (c2 >= 'Z')
+                       {
+                               c1++;
+                               c2 = 'A' - 1;
+                       }
+                       qf[2] = c1;
+                       qf[3] = ++c2;
+                       if (tTd(7, 20))
+                               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;
+                       }
+               }
+               if (c1 >= '~' && c2 >= 'Z')
+               {
+                       syserr("queuename: Cannot create \"%s\" in \"%s\"",
+                               qf, QueueDir);
+                       exit(EX_OSERR);
+               }
+               e->e_id = newstr(&qf[2]);
+               define('i', e->e_id, e);
+               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 (type == '\0')
+               return (NULL);
+       (void) sprintf(buf, "%cf%s", type, e->e_id);
+       if (tTd(7, 2))
+               printf("queuename: %s\n", buf);
+       return (buf);
+}
+\f/*
+**  UNLOCKQUEUE -- unlock the queue entry for a specified envelope
+**
+**     Parameters:
+**             e -- the envelope to unlock.
+**
+**     Returns:
+**             none
+**
+**     Side Effects:
+**             unlocks the queue for `e'.
+*/
+
+unlockqueue(e)
+       ENVELOPE *e;
+{
+       /* remove the transcript */
+# ifdef LOG
+       if (LogLevel > 19)
+               syslog(LOG_DEBUG, "%s: unlock", e->e_id);
+# endif LOG
+       if (!tTd(51, 4))
+               xunlink(queuename(e, 'x'));
+
+}
+\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);
+}