summaryrefslogtreecommitdiffstats
path: root/sendmail/src/queue.c
diff options
context:
space:
mode:
Diffstat (limited to 'sendmail/src/queue.c')
-rw-r--r--sendmail/src/queue.c8895
1 files changed, 8895 insertions, 0 deletions
diff --git a/sendmail/src/queue.c b/sendmail/src/queue.c
new file mode 100644
index 0000000..e80a035
--- /dev/null
+++ b/sendmail/src/queue.c
@@ -0,0 +1,8895 @@
+/*
+ * Copyright (c) 1998-2007 Sendmail, Inc. and its suppliers.
+ * All rights reserved.
+ * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved.
+ * Copyright (c) 1988, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * By using this file, you agree to the terms and conditions set
+ * forth in the LICENSE file which can be found at the top level of
+ * the sendmail distribution.
+ *
+ */
+
+#include <sendmail.h>
+#include <sm/sem.h>
+
+SM_RCSID("@(#)$Id: queue.c,v 8.975 2007/06/18 20:08:40 ca Exp $")
+
+#include <dirent.h>
+
+# define RELEASE_QUEUE (void) 0
+# define ST_INODE(st) (st).st_ino
+
+# define sm_file_exists(errno) ((errno) == EEXIST)
+
+# if HASFLOCK && defined(O_EXLOCK)
+# define SM_OPEN_EXLOCK 1
+# define TF_OPEN_FLAGS (O_CREAT|O_WRONLY|O_EXCL|O_EXLOCK)
+# else /* HASFLOCK && defined(O_EXLOCK) */
+# define TF_OPEN_FLAGS (O_CREAT|O_WRONLY|O_EXCL)
+# endif /* HASFLOCK && defined(O_EXLOCK) */
+
+#ifndef SM_OPEN_EXLOCK
+# define SM_OPEN_EXLOCK 0
+#endif /* ! SM_OPEN_EXLOCK */
+
+/*
+** Historical notes:
+** QF_VERSION == 4 was sendmail 8.10/8.11 without _FFR_QUEUEDELAY
+** QF_VERSION == 5 was sendmail 8.10/8.11 with _FFR_QUEUEDELAY
+** QF_VERSION == 6 was sendmail 8.12 without _FFR_QUEUEDELAY
+** QF_VERSION == 7 was sendmail 8.12 with _FFR_QUEUEDELAY
+** QF_VERSION == 8 is sendmail 8.13
+*/
+
+#define QF_VERSION 8 /* version number of this queue format */
+
+static char queue_letter __P((ENVELOPE *, int));
+static bool quarantine_queue_item __P((int, int, ENVELOPE *, char *));
+
+/* Naming convention: qgrp: index of queue group, qg: QUEUEGROUP */
+
+/*
+** Work queue.
+*/
+
+struct work
+{
+ char *w_name; /* name of control file */
+ char *w_host; /* name of recipient host */
+ bool w_lock; /* is message locked? */
+ bool w_tooyoung; /* is it too young to run? */
+ long w_pri; /* priority of message, see below */
+ time_t w_ctime; /* creation time */
+ time_t w_mtime; /* modification time */
+ int w_qgrp; /* queue group located in */
+ int w_qdir; /* queue directory located in */
+ struct work *w_next; /* next in queue */
+};
+
+typedef struct work WORK;
+
+static WORK *WorkQ; /* queue of things to be done */
+static int NumWorkGroups; /* number of work groups */
+static time_t Current_LA_time = 0;
+
+/* Get new load average every 30 seconds. */
+#define GET_NEW_LA_TIME 30
+
+#define SM_GET_LA(now) \
+ do \
+ { \
+ now = curtime(); \
+ if (Current_LA_time < now - GET_NEW_LA_TIME) \
+ { \
+ sm_getla(); \
+ Current_LA_time = now; \
+ } \
+ } while (0)
+
+/*
+** DoQueueRun indicates that a queue run is needed.
+** Notice: DoQueueRun is modified in a signal handler!
+*/
+
+static bool volatile DoQueueRun; /* non-interrupt time queue run needed */
+
+/*
+** Work group definition structure.
+** Each work group contains one or more queue groups. This is done
+** to manage the number of queue group runners active at the same time
+** to be within the constraints of MaxQueueChildren (if it is set).
+** The number of queue groups that can be run on the next work run
+** is kept track of. The queue groups are run in a round robin.
+*/
+
+struct workgrp
+{
+ int wg_numqgrp; /* number of queue groups in work grp */
+ int wg_runners; /* total runners */
+ int wg_curqgrp; /* current queue group */
+ QUEUEGRP **wg_qgs; /* array of queue groups */
+ int wg_maxact; /* max # of active runners */
+ time_t wg_lowqintvl; /* lowest queue interval */
+ int wg_restart; /* needs restarting? */
+ int wg_restartcnt; /* count of times restarted */
+};
+
+typedef struct workgrp WORKGRP;
+
+static WORKGRP volatile WorkGrp[MAXWORKGROUPS + 1]; /* work groups */
+
+#if SM_HEAP_CHECK
+static SM_DEBUG_T DebugLeakQ = SM_DEBUG_INITIALIZER("leak_q",
+ "@(#)$Debug: leak_q - trace memory leaks during queue processing $");
+#endif /* SM_HEAP_CHECK */
+
+/*
+** We use EmptyString instead of "" to avoid
+** 'zero-length format string' warnings from gcc
+*/
+
+static const char EmptyString[] = "";
+
+static void grow_wlist __P((int, int));
+static int multiqueue_cache __P((char *, int, QUEUEGRP *, int, unsigned int *));
+static int gatherq __P((int, int, bool, bool *, bool *));
+static int sortq __P((int));
+static void printctladdr __P((ADDRESS *, SM_FILE_T *));
+static bool readqf __P((ENVELOPE *, bool));
+static void restart_work_group __P((int));
+static void runner_work __P((ENVELOPE *, int, bool, int, int));
+static void schedule_queue_runs __P((bool, int, bool));
+static char *strrev __P((char *));
+static ADDRESS *setctluser __P((char *, int, ENVELOPE *));
+#if _FFR_RHS
+static int sm_strshufflecmp __P((char *, char *));
+static void init_shuffle_alphabet __P(());
+#endif /* _FFR_RHS */
+
+/*
+** Note: workcmpf?() don't use a prototype because it will cause a conflict
+** with the qsort() call (which expects something like
+** int (*compar)(const void *, const void *), not (WORK *, WORK *))
+*/
+
+static int workcmpf0();
+static int workcmpf1();
+static int workcmpf2();
+static int workcmpf3();
+static int workcmpf4();
+static int randi = 3; /* index for workcmpf5() */
+static int workcmpf5();
+static int workcmpf6();
+#if _FFR_RHS
+static int workcmpf7();
+#endif /* _FFR_RHS */
+
+#if RANDOMSHIFT
+# define get_rand_mod(m) ((get_random() >> RANDOMSHIFT) % (m))
+#else /* RANDOMSHIFT */
+# define get_rand_mod(m) (get_random() % (m))
+#endif /* RANDOMSHIFT */
+
+/*
+** File system definition.
+** Used to keep track of how much free space is available
+** on a file system in which one or more queue directories reside.
+*/
+
+typedef struct filesys_shared FILESYS;
+
+struct filesys_shared
+{
+ dev_t fs_dev; /* unique device id */
+ long fs_avail; /* number of free blocks available */
+ long fs_blksize; /* block size, in bytes */
+};
+
+/* probably kept in shared memory */
+static FILESYS FileSys[MAXFILESYS]; /* queue file systems */
+static const char *FSPath[MAXFILESYS]; /* pathnames for file systems */
+
+#if SM_CONF_SHM
+
+/*
+** Shared memory data
+**
+** Current layout:
+** size -- size of shared memory segment
+** pid -- pid of owner, should be a unique id to avoid misinterpretations
+** by other processes.
+** tag -- should be a unique id to avoid misinterpretations by others.
+** idea: hash over configuration data that will be stored here.
+** NumFileSys -- number of file systems.
+** FileSys -- (arrary of) structure for used file systems.
+** RSATmpCnt -- counter for number of uses of ephemeral RSA key.
+** QShm -- (array of) structure for information about queue directories.
+*/
+
+/*
+** Queue data in shared memory
+*/
+
+typedef struct queue_shared QUEUE_SHM_T;
+
+struct queue_shared
+{
+ int qs_entries; /* number of entries */
+ /* XXX more to follow? */
+};
+
+static void *Pshm; /* pointer to shared memory */
+static FILESYS *PtrFileSys; /* pointer to queue file system array */
+int ShmId = SM_SHM_NO_ID; /* shared memory id */
+static QUEUE_SHM_T *QShm; /* pointer to shared queue data */
+static size_t shms;
+
+# define SHM_OFF_PID(p) (((char *) (p)) + sizeof(int))
+# define SHM_OFF_TAG(p) (((char *) (p)) + sizeof(pid_t) + sizeof(int))
+# define SHM_OFF_HEAD (sizeof(pid_t) + sizeof(int) * 2)
+
+/* how to access FileSys */
+# define FILE_SYS(i) (PtrFileSys[i])
+
+/* first entry is a tag, for now just the size */
+# define OFF_FILE_SYS(p) (((char *) (p)) + SHM_OFF_HEAD)
+
+/* offset for PNumFileSys */
+# define OFF_NUM_FILE_SYS(p) (((char *) (p)) + SHM_OFF_HEAD + sizeof(FileSys))
+
+/* offset for PRSATmpCnt */
+# define OFF_RSA_TMP_CNT(p) (((char *) (p)) + SHM_OFF_HEAD + sizeof(FileSys) + sizeof(int))
+int *PRSATmpCnt;
+
+/* offset for queue_shm */
+# define OFF_QUEUE_SHM(p) (((char *) (p)) + SHM_OFF_HEAD + sizeof(FileSys) + sizeof(int) * 2)
+
+# define QSHM_ENTRIES(i) QShm[i].qs_entries
+
+/* basic size of shared memory segment */
+# define SM_T_SIZE (SHM_OFF_HEAD + sizeof(FileSys) + sizeof(int) * 2)
+
+static unsigned int hash_q __P((char *, unsigned int));
+
+/*
+** HASH_Q -- simple hash function
+**
+** Parameters:
+** p -- string to hash.
+** h -- hash start value (from previous run).
+**
+** Returns:
+** hash value.
+*/
+
+static unsigned int
+hash_q(p, h)
+ char *p;
+ unsigned int h;
+{
+ int c, d;
+
+ while (*p != '\0')
+ {
+ d = *p++;
+ c = d;
+ c ^= c<<6;
+ h += (c<<11) ^ (c>>1);
+ h ^= (d<<14) + (d<<7) + (d<<4) + d;
+ }
+ return h;
+}
+
+
+#else /* SM_CONF_SHM */
+# define FILE_SYS(i) FileSys[i]
+#endif /* SM_CONF_SHM */
+
+/* access to the various components of file system data */
+#define FILE_SYS_NAME(i) FSPath[i]
+#define FILE_SYS_AVAIL(i) FILE_SYS(i).fs_avail
+#define FILE_SYS_BLKSIZE(i) FILE_SYS(i).fs_blksize
+#define FILE_SYS_DEV(i) FILE_SYS(i).fs_dev
+
+
+/*
+** Current qf file field assignments:
+**
+** A AUTH= parameter
+** B body type
+** C controlling user
+** D data file name
+** d data file directory name (added in 8.12)
+** E error recipient
+** F flag bits
+** G free (was: queue delay algorithm if _FFR_QUEUEDELAY)
+** H header
+** I data file's inode number
+** K time of last delivery attempt
+** L Solaris Content-Length: header (obsolete)
+** M message
+** N number of delivery attempts
+** P message priority
+** q quarantine reason
+** Q original recipient (ORCPT=)
+** r final recipient (Final-Recipient: DSN field)
+** R recipient
+** S sender
+** T init time
+** V queue file version
+** X free (was: character set if _FFR_SAVE_CHARSET)
+** Y free (was: current delay if _FFR_QUEUEDELAY)
+** Z original envelope id from ESMTP
+** ! deliver by (added in 8.12)
+** $ define macro
+** . terminate file
+*/
+
+/*
+** QUEUEUP -- queue a message up for future transmission.
+**
+** Parameters:
+** e -- the envelope to queue up.
+** announce -- if true, tell when you are queueing up.
+** msync -- if true, then fsync() if SuperSafe interactive mode.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** The current request is saved in a control file.
+** The queue file is left locked.
+*/
+
+void
+queueup(e, announce, msync)
+ register ENVELOPE *e;
+ bool announce;
+ bool msync;
+{
+ register SM_FILE_T *tfp;
+ register HDR *h;
+ register ADDRESS *q;
+ int tfd = -1;
+ int i;
+ bool newid;
+ register char *p;
+ MAILER nullmailer;
+ MCI mcibuf;
+ char qf[MAXPATHLEN];
+ char tf[MAXPATHLEN];
+ char df[MAXPATHLEN];
+ char buf[MAXLINE];
+
+ /*
+ ** Create control file.
+ */
+
+#define OPEN_TF do \
+ { \
+ MODE_T oldumask = 0; \
+ \
+ if (bitset(S_IWGRP, QueueFileMode)) \
+ oldumask = umask(002); \
+ tfd = open(tf, TF_OPEN_FLAGS, QueueFileMode); \
+ if (bitset(S_IWGRP, QueueFileMode)) \
+ (void) umask(oldumask); \
+ } while (0)
+
+
+ newid = (e->e_id == NULL) || !bitset(EF_INQUEUE, e->e_flags);
+ (void) sm_strlcpy(tf, queuename(e, NEWQFL_LETTER), sizeof(tf));
+ tfp = e->e_lockfp;
+ if (tfp == NULL && newid)
+ {
+ /*
+ ** open qf file directly: this will give an error if the file
+ ** already exists and hence prevent problems if a queue-id
+ ** is reused (e.g., because the clock is set back).
+ */
+
+ (void) sm_strlcpy(tf, queuename(e, ANYQFL_LETTER), sizeof(tf));
+ OPEN_TF;
+ if (tfd < 0 ||
+#if !SM_OPEN_EXLOCK
+ !lockfile(tfd, tf, NULL, LOCK_EX|LOCK_NB) ||
+#endif /* !SM_OPEN_EXLOCK */
+ (tfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &tfd, SM_IO_WRONLY,
+ NULL)) == NULL)
+ {
+ int save_errno = errno;
+
+ printopenfds(true);
+ errno = save_errno;
+ syserr("!queueup: cannot create queue file %s, euid=%d, fd=%d, fp=%p",
+ tf, (int) geteuid(), tfd, tfp);
+ /* NOTREACHED */
+ }
+ e->e_lockfp = tfp;
+ upd_qs(e, 1, 0, "queueup");
+ }
+
+ /* if newid, write the queue file directly (instead of temp file) */
+ if (!newid)
+ {
+ /* get a locked tf file */
+ for (i = 0; i < 128; i++)
+ {
+ if (tfd < 0)
+ {
+ OPEN_TF;
+ if (tfd < 0)
+ {
+ if (errno != EEXIST)
+ break;
+ if (LogLevel > 0 && (i % 32) == 0)
+ sm_syslog(LOG_ALERT, e->e_id,
+ "queueup: cannot create %s, euid=%d: %s",
+ tf, (int) geteuid(),
+ sm_errstring(errno));
+ }
+#if SM_OPEN_EXLOCK
+ else
+ break;
+#endif /* SM_OPEN_EXLOCK */
+ }
+ if (tfd >= 0)
+ {
+#if SM_OPEN_EXLOCK
+ /* file is locked by open() */
+ break;
+#else /* SM_OPEN_EXLOCK */
+ if (lockfile(tfd, tf, NULL, LOCK_EX|LOCK_NB))
+ break;
+ else
+#endif /* SM_OPEN_EXLOCK */
+ if (LogLevel > 0 && (i % 32) == 0)
+ sm_syslog(LOG_ALERT, e->e_id,
+ "queueup: cannot lock %s: %s",
+ tf, sm_errstring(errno));
+ if ((i % 32) == 31)
+ {
+ (void) close(tfd);
+ tfd = -1;
+ }
+ }
+
+ if ((i % 32) == 31)
+ {
+ /* save the old temp file away */
+ (void) rename(tf, queuename(e, TEMPQF_LETTER));
+ }
+ else
+ (void) sleep(i % 32);
+ }
+ if (tfd < 0 || (tfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &tfd, SM_IO_WRONLY_B,
+ NULL)) == NULL)
+ {
+ int save_errno = errno;
+
+ printopenfds(true);
+ errno = save_errno;
+ syserr("!queueup: cannot create queue temp file %s, uid=%d",
+ tf, (int) geteuid());
+ }
+ }
+
+ if (tTd(40, 1))
+ sm_dprintf("\n>>>>> queueing %s/%s%s >>>>>\n",
+ qid_printqueue(e->e_qgrp, e->e_qdir),
+ queuename(e, ANYQFL_LETTER),
+ newid ? " (new id)" : "");
+ if (tTd(40, 3))
+ {
+ sm_dprintf(" e_flags=");
+ printenvflags(e);
+ }
+ if (tTd(40, 32))
+ {
+ sm_dprintf(" sendq=");
+ printaddr(sm_debug_file(), e->e_sendqueue, true);
+ }
+ if (tTd(40, 9))
+ {
+ sm_dprintf(" tfp=");
+ dumpfd(sm_io_getinfo(tfp, SM_IO_WHAT_FD, NULL), true, false);
+ sm_dprintf(" lockfp=");
+ if (e->e_lockfp == NULL)
+ sm_dprintf("NULL\n");
+ else
+ dumpfd(sm_io_getinfo(e->e_lockfp, SM_IO_WHAT_FD, NULL),
+ true, false);
+ }
+
+ /*
+ ** If there is no data file yet, create one.
+ */
+
+ (void) sm_strlcpy(df, queuename(e, DATAFL_LETTER), sizeof(df));
+ if (bitset(EF_HAS_DF, e->e_flags))
+ {
+ if (e->e_dfp != NULL &&
+ SuperSafe != SAFE_REALLY &&
+ SuperSafe != SAFE_REALLY_POSTMILTER &&
+ sm_io_setinfo(e->e_dfp, SM_BF_COMMIT, NULL) < 0 &&
+ errno != EINVAL)
+ {
+ syserr("!queueup: cannot commit data file %s, uid=%d",
+ queuename(e, DATAFL_LETTER), (int) geteuid());
+ }
+ if (e->e_dfp != NULL &&
+ SuperSafe == SAFE_INTERACTIVE && msync)
+ {
+ if (tTd(40,32))
+ sm_syslog(LOG_INFO, e->e_id,
+ "queueup: fsync(e->e_dfp)");
+
+ if (fsync(sm_io_getinfo(e->e_dfp, SM_IO_WHAT_FD,
+ NULL)) < 0)
+ {
+ if (newid)
+ syserr("!552 Error writing data file %s",
+ df);
+ else
+ syserr("!452 Error writing data file %s",
+ df);
+ }
+ }
+ }
+ else
+ {
+ int dfd;
+ MODE_T oldumask = 0;
+ register SM_FILE_T *dfp = NULL;
+ struct stat stbuf;
+
+ if (e->e_dfp != NULL &&
+ sm_io_getinfo(e->e_dfp, SM_IO_WHAT_ISTYPE, BF_FILE_TYPE))
+ syserr("committing over bf file");
+
+ if (bitset(S_IWGRP, QueueFileMode))
+ oldumask = umask(002);
+ dfd = open(df, O_WRONLY|O_CREAT|O_TRUNC|QF_O_EXTRA,
+ QueueFileMode);
+ if (bitset(S_IWGRP, QueueFileMode))
+ (void) umask(oldumask);
+ if (dfd < 0 || (dfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT,
+ (void *) &dfd, SM_IO_WRONLY_B,
+ NULL)) == NULL)
+ syserr("!queueup: cannot create data temp file %s, uid=%d",
+ df, (int) geteuid());
+ if (fstat(dfd, &stbuf) < 0)
+ e->e_dfino = -1;
+ else
+ {
+ e->e_dfdev = stbuf.st_dev;
+ e->e_dfino = ST_INODE(stbuf);
+ }
+ e->e_flags |= EF_HAS_DF;
+ memset(&mcibuf, '\0', sizeof(mcibuf));
+ mcibuf.mci_out = dfp;
+ mcibuf.mci_mailer = FileMailer;
+ (*e->e_putbody)(&mcibuf, e, NULL);
+
+ if (SuperSafe == SAFE_REALLY ||
+ SuperSafe == SAFE_REALLY_POSTMILTER ||
+ (SuperSafe == SAFE_INTERACTIVE && msync))
+ {
+ if (tTd(40,32))
+ sm_syslog(LOG_INFO, e->e_id,
+ "queueup: fsync(dfp)");
+
+ if (fsync(sm_io_getinfo(dfp, SM_IO_WHAT_FD, NULL)) < 0)
+ {
+ if (newid)
+ syserr("!552 Error writing data file %s",
+ df);
+ else
+ syserr("!452 Error writing data file %s",
+ df);
+ }
+ }
+
+ if (sm_io_close(dfp, SM_TIME_DEFAULT) < 0)
+ syserr("!queueup: cannot save data temp file %s, uid=%d",
+ df, (int) geteuid());
+ e->e_putbody = putbody;
+ }
+
+ /*
+ ** Output future work requests.
+ ** Priority and creation time should be first, since
+ ** they are required by gatherq.
+ */
+
+ /* output queue version number (must be first!) */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "V%d\n", QF_VERSION);
+
+ /* output creation time */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "T%ld\n", (long) e->e_ctime);
+
+ /* output last delivery time */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "K%ld\n", (long) e->e_dtime);
+
+ /* output number of delivery attempts */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "N%d\n", e->e_ntries);
+
+ /* output message priority */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "P%ld\n", e->e_msgpriority);
+
+ /*
+ ** If data file is in a different directory than the queue file,
+ ** output a "d" record naming the directory of the data file.
+ */
+
+ if (e->e_dfqgrp != e->e_qgrp)
+ {
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "d%s\n",
+ Queue[e->e_dfqgrp]->qg_qpaths[e->e_dfqdir].qp_name);
+ }
+
+ /* output inode number of data file */
+ /* XXX should probably include device major/minor too */
+ if (e->e_dfino != -1)
+ {
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "I%ld/%ld/%llu\n",
+ (long) major(e->e_dfdev),
+ (long) minor(e->e_dfdev),
+ (ULONGLONG_T) e->e_dfino);
+ }
+
+ /* output body type */
+ if (e->e_bodytype != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "B%s\n",
+ denlstring(e->e_bodytype, true, false));
+
+ /* quarantine reason */
+ if (e->e_quarmsg != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "q%s\n",
+ denlstring(e->e_quarmsg, true, false));
+
+ /* message from envelope, if it exists */
+ if (e->e_message != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "M%s\n",
+ denlstring(e->e_message, true, false));
+
+ /* send various flag bits through */
+ p = buf;
+ if (bitset(EF_WARNING, e->e_flags))
+ *p++ = 'w';
+ if (bitset(EF_RESPONSE, e->e_flags))
+ *p++ = 'r';
+ if (bitset(EF_HAS8BIT, e->e_flags))
+ *p++ = '8';
+ if (bitset(EF_DELETE_BCC, e->e_flags))
+ *p++ = 'b';
+ if (bitset(EF_RET_PARAM, e->e_flags))
+ *p++ = 'd';
+ if (bitset(EF_NO_BODY_RETN, e->e_flags))
+ *p++ = 'n';
+ if (bitset(EF_SPLIT, e->e_flags))
+ *p++ = 's';
+ *p++ = '\0';
+ if (buf[0] != '\0')
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "F%s\n", buf);
+
+ /* save $={persistentMacros} macro values */
+ queueup_macros(macid("{persistentMacros}"), tfp, e);
+
+ /* output name of sender */
+ if (bitnset(M_UDBENVELOPE, e->e_from.q_mailer->m_flags))
+ p = e->e_sender;
+ else
+ p = e->e_from.q_paddr;
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "S%s\n",
+ denlstring(p, true, false));
+
+ /* output ESMTP-supplied "original" information */
+ if (e->e_envid != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "Z%s\n",
+ denlstring(e->e_envid, true, false));
+
+ /* output AUTH= parameter */
+ if (e->e_auth_param != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "A%s\n",
+ denlstring(e->e_auth_param, true, false));
+ if (e->e_dlvr_flag != 0)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "!%c %ld\n",
+ (char) e->e_dlvr_flag, e->e_deliver_by);
+
+ /* output list of recipient addresses */
+ printctladdr(NULL, NULL);
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (!QS_IS_UNDELIVERED(q->q_state))
+ continue;
+
+ /* message for this recipient, if it exists */
+ if (q->q_message != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "M%s\n",
+ denlstring(q->q_message, true,
+ false));
+
+ printctladdr(q, tfp);
+ if (q->q_orcpt != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "Q%s\n",
+ denlstring(q->q_orcpt, true,
+ false));
+ if (q->q_finalrcpt != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "r%s\n",
+ denlstring(q->q_finalrcpt, true,
+ false));
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'R');
+ if (bitset(QPRIMARY, q->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'P');
+ if (bitset(QHASNOTIFY, q->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'N');
+ if (bitset(QPINGONSUCCESS, q->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'S');
+ if (bitset(QPINGONFAILURE, q->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'F');
+ if (bitset(QPINGONDELAY, q->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'D');
+ if (q->q_alias != NULL &&
+ bitset(QALIAS, q->q_alias->q_flags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, 'A');
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, ':');
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "%s\n",
+ denlstring(q->q_paddr, true, false));
+ if (announce)
+ {
+ char *tag = "queued";
+
+ if (e->e_quarmsg != NULL)
+ tag = "quarantined";
+
+ e->e_to = q->q_paddr;
+ message(tag);
+ if (LogLevel > 8)
+ logdelivery(q->q_mailer, NULL, q->q_status,
+ tag, NULL, (time_t) 0, e);
+ e->e_to = NULL;
+ }
+ if (tTd(40, 1))
+ {
+ sm_dprintf("queueing ");
+ printaddr(sm_debug_file(), q, false);
+ }
+ }
+
+ /*
+ ** 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.
+ */
+
+ memset((char *) &nullmailer, '\0', sizeof(nullmailer));
+ nullmailer.m_re_rwset = nullmailer.m_rh_rwset =
+ nullmailer.m_se_rwset = nullmailer.m_sh_rwset = -1;
+ nullmailer.m_eol = "\n";
+ memset(&mcibuf, '\0', sizeof(mcibuf));
+ mcibuf.mci_mailer = &nullmailer;
+ mcibuf.mci_out = tfp;
+
+ macdefine(&e->e_macro, A_PERM, 'g', "\201f");
+ for (h = e->e_header; h != NULL; h = h->h_link)
+ {
+ if (h->h_value == NULL)
+ continue;
+
+ /* don't output resent headers on non-resent messages */
+ if (bitset(H_RESENT, h->h_flags) &&
+ !bitset(EF_RESENT, e->e_flags))
+ continue;
+
+ /* expand macros; if null, don't output header at all */
+ if (bitset(H_DEFAULT, h->h_flags))
+ {
+ (void) expand(h->h_value, buf, sizeof(buf), e);
+ if (buf[0] == '\0')
+ continue;
+ if (buf[0] == ' ' && buf[1] == '\0')
+ continue;
+ }
+
+ /* output this header */
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "H?");
+
+ /* output conditional macro if present */
+ if (h->h_macro != '\0')
+ {
+ if (bitset(0200, h->h_macro))
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT,
+ "${%s}",
+ macname(bitidx(h->h_macro)));
+ else
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT,
+ "$%c", h->h_macro);
+ }
+ else if (!bitzerop(h->h_mflags) &&
+ bitset(H_CHECK|H_ACHECK, h->h_flags))
+ {
+ int j;
+
+ /* if conditional, output the set of conditions */
+ for (j = '\0'; j <= '\177'; j++)
+ if (bitnset(j, h->h_mflags))
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT,
+ j);
+ }
+ (void) sm_io_putc(tfp, SM_TIME_DEFAULT, '?');
+
+ /* output the header: expand macros, convert addresses */
+ if (bitset(H_DEFAULT, h->h_flags) &&
+ !bitset(H_BINDLATE, h->h_flags))
+ {
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "%s:%s\n",
+ h->h_field,
+ denlstring(buf, false, true));
+ }
+ else if (bitset(H_FROM|H_RCPT, h->h_flags) &&
+ !bitset(H_BINDLATE, h->h_flags))
+ {
+ bool oldstyle = bitset(EF_OLDSTYLE, e->e_flags);
+ SM_FILE_T *savetrace = TrafficLogFile;
+
+ TrafficLogFile = NULL;
+
+ if (bitset(H_FROM, h->h_flags))
+ oldstyle = false;
+ commaize(h, h->h_value, oldstyle, &mcibuf, e,
+ PXLF_HEADER);
+
+ TrafficLogFile = savetrace;
+ }
+ else
+ {
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "%s:%s\n",
+ h->h_field,
+ denlstring(h->h_value, false,
+ true));
+ }
+ }
+
+ /*
+ ** Clean up.
+ **
+ ** Write a terminator record -- this is to prevent
+ ** scurrilous crackers from appending any data.
+ */
+
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, ".\n");
+
+ if (sm_io_flush(tfp, SM_TIME_DEFAULT) != 0 ||
+ ((SuperSafe == SAFE_REALLY ||
+ SuperSafe == SAFE_REALLY_POSTMILTER ||
+ (SuperSafe == SAFE_INTERACTIVE && msync)) &&
+ fsync(sm_io_getinfo(tfp, SM_IO_WHAT_FD, NULL)) < 0) ||
+ sm_io_error(tfp))
+ {
+ if (newid)
+ syserr("!552 Error writing control file %s", tf);
+ else
+ syserr("!452 Error writing control file %s", tf);
+ }
+
+ if (!newid)
+ {
+ char new = queue_letter(e, ANYQFL_LETTER);
+
+ /* rename (locked) tf to be (locked) [qh]f */
+ (void) sm_strlcpy(qf, queuename(e, ANYQFL_LETTER),
+ sizeof(qf));
+ if (rename(tf, qf) < 0)
+ syserr("cannot rename(%s, %s), uid=%d",
+ tf, qf, (int) geteuid());
+ else
+ {
+ /*
+ ** Check if type has changed and only
+ ** remove the old item if the rename above
+ ** succeeded.
+ */
+
+ if (e->e_qfletter != '\0' &&
+ e->e_qfletter != new)
+ {
+ if (tTd(40, 5))
+ {
+ sm_dprintf("type changed from %c to %c\n",
+ e->e_qfletter, new);
+ }
+
+ if (unlink(queuename(e, e->e_qfletter)) < 0)
+ {
+ /* XXX: something more drastic? */
+ if (LogLevel > 0)
+ sm_syslog(LOG_ERR, e->e_id,
+ "queueup: unlink(%s) failed: %s",
+ queuename(e, e->e_qfletter),
+ sm_errstring(errno));
+ }
+ }
+ }
+ e->e_qfletter = new;
+
+ /*
+ ** fsync() after renaming to make sure metadata is
+ ** written to disk on filesystems in which renames are
+ ** not guaranteed.
+ */
+
+ if (SuperSafe != SAFE_NO)
+ {
+ /* for softupdates */
+ if (tfd >= 0 && fsync(tfd) < 0)
+ {
+ syserr("!queueup: cannot fsync queue temp file %s",
+ tf);
+ }
+ SYNC_DIR(qf, true);
+ }
+
+ /* close and unlock old (locked) queue file */
+ if (e->e_lockfp != NULL)
+ (void) sm_io_close(e->e_lockfp, SM_TIME_DEFAULT);
+ e->e_lockfp = tfp;
+
+ /* save log info */
+ if (LogLevel > 79)
+ sm_syslog(LOG_DEBUG, e->e_id, "queueup %s", qf);
+ }
+ else
+ {
+ /* save log info */
+ if (LogLevel > 79)
+ sm_syslog(LOG_DEBUG, e->e_id, "queueup %s", tf);
+
+ e->e_qfletter = queue_letter(e, ANYQFL_LETTER);
+ }
+
+ errno = 0;
+ e->e_flags |= EF_INQUEUE;
+
+ if (tTd(40, 1))
+ sm_dprintf("<<<<< done queueing %s <<<<<\n\n", e->e_id);
+ return;
+}
+
+/*
+** PRINTCTLADDR -- print control address to file.
+**
+** Parameters:
+** a -- address.
+** tfp -- file pointer.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** The control address (if changed) is printed to the file.
+** The last control address and uid are saved.
+*/
+
+static void
+printctladdr(a, tfp)
+ register ADDRESS *a;
+ SM_FILE_T *tfp;
+{
+ char *user;
+ register ADDRESS *q;
+ uid_t uid;
+ gid_t gid;
+ static ADDRESS *lastctladdr = NULL;
+ static uid_t lastuid;
+
+ /* initialization */
+ if (a == NULL || a->q_alias == NULL || tfp == NULL)
+ {
+ if (lastctladdr != NULL && tfp != NULL)
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "C\n");
+ lastctladdr = NULL;
+ lastuid = 0;
+ return;
+ }
+
+ /* find the active uid */
+ q = getctladdr(a);
+ if (q == NULL)
+ {
+ user = NULL;
+ uid = 0;
+ gid = 0;
+ }
+ else
+ {
+ user = q->q_ruser != NULL ? q->q_ruser : q->q_user;
+ uid = q->q_uid;
+ gid = q->q_gid;
+ }
+ a = a->q_alias;
+
+ /* check to see if this is the same as last time */
+ if (lastctladdr != NULL && uid == lastuid &&
+ strcmp(lastctladdr->q_paddr, a->q_paddr) == 0)
+ return;
+ lastuid = uid;
+ lastctladdr = a;
+
+ if (uid == 0 || user == NULL || user[0] == '\0')
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "C");
+ else
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, "C%s:%ld:%ld",
+ denlstring(user, true, false), (long) uid,
+ (long) gid);
+ (void) sm_io_fprintf(tfp, SM_TIME_DEFAULT, ":%s\n",
+ denlstring(a->q_paddr, true, false));
+}
+
+/*
+** RUNNERS_SIGTERM -- propagate a SIGTERM to queue runner process
+**
+** This propagates the signal to the child processes that are queue
+** runners. This is for a queue runner "cleanup". After all of the
+** child queue runner processes are signaled (it should be SIGTERM
+** being the sig) then the old signal handler (Oldsh) is called
+** to handle any cleanup set for this process (provided it is not
+** SIG_DFL or SIG_IGN). The signal may not be handled immediately
+** if the BlockOldsh flag is set. If the current process doesn't
+** have a parent then handle the signal immediately, regardless of
+** BlockOldsh.
+**
+** Parameters:
+** sig -- the signal number being sent
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets the NoMoreRunners boolean to true to stop more runners
+** from being started in runqueue().
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+static bool volatile NoMoreRunners = false;
+static sigfunc_t Oldsh_term = SIG_DFL;
+static sigfunc_t Oldsh_hup = SIG_DFL;
+static sigfunc_t volatile Oldsh = SIG_DFL;
+static bool BlockOldsh = false;
+static int volatile Oldsig = 0;
+static SIGFUNC_DECL runners_sigterm __P((int));
+static SIGFUNC_DECL runners_sighup __P((int));
+
+static SIGFUNC_DECL
+runners_sigterm(sig)
+ int sig;
+{
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, runners_sigterm);
+ errno = save_errno;
+ CHECK_CRITICAL(sig);
+ NoMoreRunners = true;
+ Oldsh = Oldsh_term;
+ Oldsig = sig;
+ proc_list_signal(PROC_QUEUE, sig);
+
+ if (!BlockOldsh || getppid() <= 1)
+ {
+ /* Check that a valid 'old signal handler' is callable */
+ if (Oldsh_term != SIG_DFL && Oldsh_term != SIG_IGN &&
+ Oldsh_term != runners_sigterm)
+ (*Oldsh_term)(sig);
+ }
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** RUNNERS_SIGHUP -- propagate a SIGHUP to queue runner process
+**
+** This propagates the signal to the child processes that are queue
+** runners. This is for a queue runner "cleanup". After all of the
+** child queue runner processes are signaled (it should be SIGHUP
+** being the sig) then the old signal handler (Oldsh) is called to
+** handle any cleanup set for this process (provided it is not SIG_DFL
+** or SIG_IGN). The signal may not be handled immediately if the
+** BlockOldsh flag is set. If the current process doesn't have
+** a parent then handle the signal immediately, regardless of
+** BlockOldsh.
+**
+** Parameters:
+** sig -- the signal number being sent
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Sets the NoMoreRunners boolean to true to stop more runners
+** from being started in runqueue().
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+static SIGFUNC_DECL
+runners_sighup(sig)
+ int sig;
+{
+ int save_errno = errno;
+
+ FIX_SYSV_SIGNAL(sig, runners_sighup);
+ errno = save_errno;
+ CHECK_CRITICAL(sig);
+ NoMoreRunners = true;
+ Oldsh = Oldsh_hup;
+ Oldsig = sig;
+ proc_list_signal(PROC_QUEUE, sig);
+
+ if (!BlockOldsh || getppid() <= 1)
+ {
+ /* Check that a valid 'old signal handler' is callable */
+ if (Oldsh_hup != SIG_DFL && Oldsh_hup != SIG_IGN &&
+ Oldsh_hup != runners_sighup)
+ (*Oldsh_hup)(sig);
+ }
+ errno = save_errno;
+ return SIGFUNC_RETURN;
+}
+/*
+** MARK_WORK_GROUP_RESTART -- mark a work group as needing a restart
+**
+** Sets a workgroup for restarting.
+**
+** Parameters:
+** wgrp -- the work group id to restart.
+** reason -- why (signal?), -1 to turn off restart
+**
+** Returns:
+** none.
+**
+** Side effects:
+** May set global RestartWorkGroup to true.
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+void
+mark_work_group_restart(wgrp, reason)
+ int wgrp;
+ int reason;
+{
+ if (wgrp < 0 || wgrp > NumWorkGroups)
+ return;
+
+ WorkGrp[wgrp].wg_restart = reason;
+ if (reason >= 0)
+ RestartWorkGroup = true;
+}
+/*
+** RESTART_MARKED_WORK_GROUPS -- restart work groups marked as needing restart
+**
+** Restart any workgroup marked as needing a restart provided more
+** runners are allowed.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side effects:
+** Sets global RestartWorkGroup to false.
+*/
+
+void
+restart_marked_work_groups()
+{
+ int i;
+ int wasblocked;
+
+ if (NoMoreRunners)
+ return;
+
+ /* Block SIGCHLD so reapchild() doesn't mess with us */
+ wasblocked = sm_blocksignal(SIGCHLD);
+
+ for (i = 0; i < NumWorkGroups; i++)
+ {
+ if (WorkGrp[i].wg_restart >= 0)
+ {
+ if (LogLevel > 8)
+ sm_syslog(LOG_ERR, NOQID,
+ "restart queue runner=%d due to signal 0x%x",
+ i, WorkGrp[i].wg_restart);
+ restart_work_group(i);
+ }
+ }
+ RestartWorkGroup = false;
+
+ if (wasblocked == 0)
+ (void) sm_releasesignal(SIGCHLD);
+}
+/*
+** RESTART_WORK_GROUP -- restart a specific work group
+**
+** Restart a specific workgroup provided more runners are allowed.
+** If the requested work group has been restarted too many times log
+** this and refuse to restart.
+**
+** Parameters:
+** wgrp -- the work group id to restart
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** starts another process doing the work of wgrp
+*/
+
+#define MAX_PERSIST_RESTART 10 /* max allowed number of restarts */
+
+static void
+restart_work_group(wgrp)
+ int wgrp;
+{
+ if (NoMoreRunners ||
+ wgrp < 0 || wgrp > NumWorkGroups)
+ return;
+
+ WorkGrp[wgrp].wg_restart = -1;
+ if (WorkGrp[wgrp].wg_restartcnt < MAX_PERSIST_RESTART)
+ {
+ /* avoid overflow; increment here */
+ WorkGrp[wgrp].wg_restartcnt++;
+ (void) run_work_group(wgrp, RWG_FORK|RWG_PERSISTENT|RWG_RUNALL);
+ }
+ else
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "ERROR: persistent queue runner=%d restarted too many times, queue runner lost",
+ wgrp);
+ }
+}
+/*
+** SCHEDULE_QUEUE_RUNS -- schedule the next queue run for a work group.
+**
+** Parameters:
+** runall -- schedule even if individual bit is not set.
+** wgrp -- the work group id to schedule.
+** didit -- the queue run was performed for this work group.
+**
+** Returns:
+** nothing
+*/
+
+#define INCR_MOD(v, m) if (++v >= m) \
+ v = 0; \
+ else
+
+static void
+schedule_queue_runs(runall, wgrp, didit)
+ bool runall;
+ int wgrp;
+ bool didit;
+{
+ int qgrp, cgrp, endgrp;
+#if _FFR_QUEUE_SCHED_DBG
+ time_t lastsched;
+ bool sched;
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ time_t now;
+ time_t minqintvl;
+
+ /*
+ ** This is a bit ugly since we have to duplicate the
+ ** code that "walks" through a work queue group.
+ */
+
+ now = curtime();
+ minqintvl = 0;
+ cgrp = endgrp = WorkGrp[wgrp].wg_curqgrp;
+ do
+ {
+ time_t qintvl;
+
+#if _FFR_QUEUE_SCHED_DBG
+ lastsched = 0;
+ sched = false;
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ qgrp = WorkGrp[wgrp].wg_qgs[cgrp]->qg_index;
+ if (Queue[qgrp]->qg_queueintvl > 0)
+ qintvl = Queue[qgrp]->qg_queueintvl;
+ else if (QueueIntvl > 0)
+ qintvl = QueueIntvl;
+ else
+ qintvl = (time_t) 0;
+#if _FFR_QUEUE_SCHED_DBG
+ lastsched = Queue[qgrp]->qg_nextrun;
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ if ((runall || Queue[qgrp]->qg_nextrun <= now) && qintvl > 0)
+ {
+#if _FFR_QUEUE_SCHED_DBG
+ sched = true;
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ if (minqintvl == 0 || qintvl < minqintvl)
+ minqintvl = qintvl;
+
+ /*
+ ** Only set a new time if a queue run was performed
+ ** for this queue group. If the queue was not run,
+ ** we could starve it by setting a new time on each
+ ** call.
+ */
+
+ if (didit)
+ Queue[qgrp]->qg_nextrun += qintvl;
+ }
+#if _FFR_QUEUE_SCHED_DBG
+ if (tTd(69, 10))
+ sm_syslog(LOG_INFO, NOQID,
+ "sqr: wgrp=%d, cgrp=%d, qgrp=%d, intvl=%ld, QI=%ld, runall=%d, lastrun=%ld, nextrun=%ld, sched=%d",
+ wgrp, cgrp, qgrp, Queue[qgrp]->qg_queueintvl,
+ QueueIntvl, runall, lastsched,
+ Queue[qgrp]->qg_nextrun, sched);
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ INCR_MOD(cgrp, WorkGrp[wgrp].wg_numqgrp);
+ } while (endgrp != cgrp);
+ if (minqintvl > 0)
+ (void) sm_setevent(minqintvl, runqueueevent, 0);
+}
+
+#if _FFR_QUEUE_RUN_PARANOIA
+/*
+** CHECKQUEUERUNNER -- check whether a queue group hasn't been run.
+**
+** Use this if events may get lost and hence queue runners may not
+** be started and mail will pile up in a queue.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** true if a queue run is necessary.
+**
+** Side Effects:
+** may schedule a queue run.
+*/
+
+bool
+checkqueuerunner()
+{
+ int qgrp;
+ time_t now, minqintvl;
+
+ now = curtime();
+ minqintvl = 0;
+ for (qgrp = 0; qgrp < NumQueue && Queue[qgrp] != NULL; qgrp++)
+ {
+ time_t qintvl;
+
+ if (Queue[qgrp]->qg_queueintvl > 0)
+ qintvl = Queue[qgrp]->qg_queueintvl;
+ else if (QueueIntvl > 0)
+ qintvl = QueueIntvl;
+ else
+ qintvl = (time_t) 0;
+ if (Queue[qgrp]->qg_nextrun <= now - qintvl)
+ {
+ if (minqintvl == 0 || qintvl < minqintvl)
+ minqintvl = qintvl;
+ if (LogLevel > 1)
+ sm_syslog(LOG_WARNING, NOQID,
+ "checkqueuerunner: queue %d should have been run at %s, queue interval %ld",
+ qgrp,
+ arpadate(ctime(&Queue[qgrp]->qg_nextrun)),
+ qintvl);
+ }
+ }
+ if (minqintvl > 0)
+ {
+ (void) sm_setevent(minqintvl, runqueueevent, 0);
+ return true;
+ }
+ return false;
+}
+#endif /* _FFR_QUEUE_RUN_PARANOIA */
+
+/*
+** RUNQUEUE -- run the jobs in the queue.
+**
+** Gets the stuff out of the queue in some presumably logical
+** order and processes them.
+**
+** Parameters:
+** 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.
+** false can be ignored if we have multiple queues.
+** verbose -- if true, print out status information.
+** persistent -- persistent queue runner?
+** runall -- run all groups or only a subset (DoQueueRun)?
+**
+** Returns:
+** true if the queue run successfully began.
+**
+** Side Effects:
+** runs things in the mail queue using run_work_group().
+** maybe schedules next queue run.
+*/
+
+static ENVELOPE QueueEnvelope; /* the queue run envelope */
+static time_t LastQueueTime = 0; /* last time a queue ID assigned */
+static pid_t LastQueuePid = -1; /* last PID which had a queue ID */
+
+/* values for qp_supdirs */
+#define QP_NOSUB 0x0000 /* No subdirectories */
+#define QP_SUBDF 0x0001 /* "df" subdirectory */
+#define QP_SUBQF 0x0002 /* "qf" subdirectory */
+#define QP_SUBXF 0x0004 /* "xf" subdirectory */
+
+bool
+runqueue(forkflag, verbose, persistent, runall)
+ bool forkflag;
+ bool verbose;
+ bool persistent;
+ bool runall;
+{
+ int i;
+ bool ret = true;
+ static int curnum = 0;
+ sigfunc_t cursh;
+#if SM_HEAP_CHECK
+ SM_NONVOLATILE int oldgroup = 0;
+
+ if (sm_debug_active(&DebugLeakQ, 1))
+ {
+ oldgroup = sm_heap_group();
+ sm_heap_newgroup();
+ sm_dprintf("runqueue() heap group #%d\n", sm_heap_group());
+ }
+#endif /* SM_HEAP_CHECK */
+
+ /* queue run has been started, don't do any more this time */
+ DoQueueRun = false;
+
+ /* more than one queue or more than one directory per queue */
+ if (!forkflag && !verbose &&
+ (WorkGrp[0].wg_qgs[0]->qg_numqueues > 1 || NumWorkGroups > 1 ||
+ WorkGrp[0].wg_numqgrp > 1))
+ forkflag = true;
+
+ /*
+ ** For controlling queue runners via signals sent to this process.
+ ** Oldsh* will get called too by runners_sig* (if it is not SIG_IGN
+ ** or SIG_DFL) to preserve cleanup behavior. Now that this process
+ ** will have children (and perhaps grandchildren) this handler will
+ ** be left in place. This is because this process, once it has
+ ** finished spinning off queue runners, may go back to doing something
+ ** else (like being a daemon). And we still want on a SIG{TERM,HUP} to
+ ** clean up the child queue runners. Only install 'runners_sig*' once
+ ** else we'll get stuck looping forever.
+ */
+
+ cursh = sm_signal(SIGTERM, runners_sigterm);
+ if (cursh != runners_sigterm)
+ Oldsh_term = cursh;
+ cursh = sm_signal(SIGHUP, runners_sighup);
+ if (cursh != runners_sighup)
+ Oldsh_hup = cursh;
+
+ for (i = 0; i < NumWorkGroups && !NoMoreRunners; i++)
+ {
+ int rwgflags = RWG_NONE;
+
+ /*
+ ** If MaxQueueChildren active then test whether the start
+ ** of the next queue group's additional queue runners (maximum)
+ ** will result in MaxQueueChildren being exceeded.
+ **
+ ** Note: do not use continue; even though another workgroup
+ ** may have fewer queue runners, this would be "unfair",
+ ** i.e., this work group might "starve" then.
+ */
+
+#if _FFR_QUEUE_SCHED_DBG
+ if (tTd(69, 10))
+ sm_syslog(LOG_INFO, NOQID,
+ "rq: curnum=%d, MaxQueueChildren=%d, CurRunners=%d, WorkGrp[curnum].wg_maxact=%d",
+ curnum, MaxQueueChildren, CurRunners,
+ WorkGrp[curnum].wg_maxact);
+#endif /* _FFR_QUEUE_SCHED_DBG */
+ if (MaxQueueChildren > 0 &&
+ CurRunners + WorkGrp[curnum].wg_maxact > MaxQueueChildren)
+ break;
+
+ /*
+ ** Pick up where we left off (curnum), in case we
+ ** used up all the children last time without finishing.
+ ** This give a round-robin fairness to queue runs.
+ **
+ ** Increment CurRunners before calling run_work_group()
+ ** to avoid a "race condition" with proc_list_drop() which
+ ** decrements CurRunners if the queue runners terminate.
+ ** Notice: CurRunners is an upper limit, in some cases
+ ** (too few jobs in the queue) this value is larger than
+ ** the actual number of queue runners. The discrepancy can
+ ** increase if some queue runners "hang" for a long time.
+ */
+
+ CurRunners += WorkGrp[curnum].wg_maxact;
+ if (forkflag)
+ rwgflags |= RWG_FORK;
+ if (verbose)
+ rwgflags |= RWG_VERBOSE;
+ if (persistent)
+ rwgflags |= RWG_PERSISTENT;
+ if (runall)
+ rwgflags |= RWG_RUNALL;
+ ret = run_work_group(curnum, rwgflags);
+
+ /*
+ ** Failure means a message was printed for ETRN
+ ** and subsequent queues are likely to fail as well.
+ ** Decrement CurRunners in that case because
+ ** none have been started.
+ */
+
+ if (!ret)
+ {
+ CurRunners -= WorkGrp[curnum].wg_maxact;
+ break;
+ }
+
+ if (!persistent)
+ schedule_queue_runs(runall, curnum, true);
+ INCR_MOD(curnum, NumWorkGroups);
+ }
+
+ /* schedule left over queue runs */
+ if (i < NumWorkGroups && !NoMoreRunners && !persistent)
+ {
+ int h;
+
+ for (h = curnum; i < NumWorkGroups; i++)
+ {
+ schedule_queue_runs(runall, h, false);
+ INCR_MOD(h, NumWorkGroups);
+ }
+ }
+
+
+#if SM_HEAP_CHECK
+ if (sm_debug_active(&DebugLeakQ, 1))
+ sm_heap_setgroup(oldgroup);
+#endif /* SM_HEAP_CHECK */
+ return ret;
+}
+
+#if _FFR_SKIP_DOMAINS
+/*
+** SKIP_DOMAINS -- Skip 'skip' number of domains in the WorkQ.
+**
+** Added by Stephen Frost <sfrost@snowman.net> to support
+** having each runner process every N'th domain instead of
+** every N'th message.
+**
+** Parameters:
+** skip -- number of domains in WorkQ to skip.
+**
+** Returns:
+** total number of messages skipped.
+**
+** Side Effects:
+** may change WorkQ
+*/
+
+static int
+skip_domains(skip)
+ int skip;
+{
+ int n, seqjump;
+
+ for (n = 0, seqjump = 0; n < skip && WorkQ != NULL; seqjump++)
+ {
+ if (WorkQ->w_next != NULL)
+ {
+ if (WorkQ->w_host != NULL &&
+ WorkQ->w_next->w_host != NULL)
+ {
+ if (sm_strcasecmp(WorkQ->w_host,
+ WorkQ->w_next->w_host) != 0)
+ n++;
+ }
+ else
+ {
+ if ((WorkQ->w_host != NULL &&
+ WorkQ->w_next->w_host == NULL) ||
+ (WorkQ->w_host == NULL &&
+ WorkQ->w_next->w_host != NULL))
+ n++;
+ }
+ }
+ WorkQ = WorkQ->w_next;
+ }
+ return seqjump;
+}
+#endif /* _FFR_SKIP_DOMAINS */
+
+/*
+** RUNNER_WORK -- have a queue runner do its work
+**
+** Have a queue runner do its work a list of entries.
+** When work isn't directly being done then this process can take a signal
+** and terminate immediately (in a clean fashion of course).
+** When work is directly being done, it's not to be interrupted
+** immediately: the work should be allowed to finish at a clean point
+** before termination (in a clean fashion of course).
+**
+** Parameters:
+** e -- envelope.
+** sequenceno -- 'th process to run WorkQ.
+** didfork -- did the calling process fork()?
+** skip -- process only each skip'th item.
+** njobs -- number of jobs in WorkQ.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** runs things in the mail queue.
+*/
+
+static void
+runner_work(e, sequenceno, didfork, skip, njobs)
+ register ENVELOPE *e;
+ int sequenceno;
+ bool didfork;
+ int skip;
+ int njobs;
+{
+ int n, seqjump;
+ WORK *w;
+ time_t now;
+
+ SM_GET_LA(now);
+
+ /*
+ ** Here we temporarily block the second calling of the handlers.
+ ** This allows us to handle the signal without terminating in the
+ ** middle of direct work. If a signal does come, the test for
+ ** NoMoreRunners will find it.
+ */
+
+ BlockOldsh = true;
+ seqjump = skip;
+
+ /* process them once at a time */
+ while (WorkQ != NULL)
+ {
+#if SM_HEAP_CHECK
+ SM_NONVOLATILE int oldgroup = 0;
+
+ if (sm_debug_active(&DebugLeakQ, 1))
+ {
+ oldgroup = sm_heap_group();
+ sm_heap_newgroup();
+ sm_dprintf("run_queue_group() heap group #%d\n",
+ sm_heap_group());
+ }
+#endif /* SM_HEAP_CHECK */
+
+ /* do no more work */
+ if (NoMoreRunners)
+ {
+ /* Check that a valid signal handler is callable */
+ if (Oldsh != SIG_DFL && Oldsh != SIG_IGN &&
+ Oldsh != runners_sighup &&
+ Oldsh != runners_sigterm)
+ (*Oldsh)(Oldsig);
+ break;
+ }
+
+ w = WorkQ; /* assign current work item */
+
+ /*
+ ** Set the head of the WorkQ to the next work item.
+ ** It is set 'skip' ahead (the number of parallel queue
+ ** runners working on WorkQ together) since each runner
+ ** works on every 'skip'th (N-th) item.
+#if _FFR_SKIP_DOMAINS
+ ** In the case of the BYHOST Queue Sort Order, the 'item'
+ ** is a domain, so we work on every 'skip'th (N-th) domain.
+#endif * _FFR_SKIP_DOMAINS *
+ */
+
+#if _FFR_SKIP_DOMAINS
+ if (QueueSortOrder == QSO_BYHOST)
+ {
+ seqjump = 1;
+ if (WorkQ->w_next != NULL)
+ {
+ if (WorkQ->w_host != NULL &&
+ WorkQ->w_next->w_host != NULL)
+ {
+ if (sm_strcasecmp(WorkQ->w_host,
+ WorkQ->w_next->w_host)
+ != 0)
+ seqjump = skip_domains(skip);
+ else
+ WorkQ = WorkQ->w_next;
+ }
+ else
+ {
+ if ((WorkQ->w_host != NULL &&
+ WorkQ->w_next->w_host == NULL) ||
+ (WorkQ->w_host == NULL &&
+ WorkQ->w_next->w_host != NULL))
+ seqjump = skip_domains(skip);
+ else
+ WorkQ = WorkQ->w_next;
+ }
+ }
+ else
+ WorkQ = WorkQ->w_next;
+ }
+ else
+#endif /* _FFR_SKIP_DOMAINS */
+ {
+ for (n = 0; n < skip && WorkQ != NULL; n++)
+ WorkQ = WorkQ->w_next;
+ }
+
+ e->e_to = NULL;
+
+ /*
+ ** Ignore jobs that are too expensive for the moment.
+ **
+ ** Get new load average every GET_NEW_LA_TIME seconds.
+ */
+
+ SM_GET_LA(now);
+ if (shouldqueue(WkRecipFact, Current_LA_time))
+ {
+ char *msg = "Aborting queue run: load average too high";
+
+ if (Verbose)
+ message("%s", msg);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, "runqueue: %s", msg);
+ break;
+ }
+ if (shouldqueue(w->w_pri, w->w_ctime))
+ {
+ if (Verbose)
+ message(EmptyString);
+ if (QueueSortOrder == QSO_BYPRIORITY)
+ {
+ if (Verbose)
+ message("Skipping %s/%s (sequence %d of %d) and flushing rest of queue",
+ qid_printqueue(w->w_qgrp,
+ w->w_qdir),
+ w->w_name + 2, sequenceno,
+ njobs);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID,
+ "runqueue: Flushing queue from %s/%s (pri %ld, LA %d, %d of %d)",
+ qid_printqueue(w->w_qgrp,
+ w->w_qdir),
+ w->w_name + 2, w->w_pri,
+ CurrentLA, sequenceno,
+ njobs);
+ break;
+ }
+ else if (Verbose)
+ message("Skipping %s/%s (sequence %d of %d)",
+ qid_printqueue(w->w_qgrp, w->w_qdir),
+ w->w_name + 2, sequenceno, njobs);
+ }
+ else
+ {
+ if (Verbose)
+ {
+ message(EmptyString);
+ message("Running %s/%s (sequence %d of %d)",
+ qid_printqueue(w->w_qgrp, w->w_qdir),
+ w->w_name + 2, sequenceno, njobs);
+ }
+ if (didfork && MaxQueueChildren > 0)
+ {
+ sm_blocksignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, reapchild);
+ }
+ if (tTd(63, 100))
+ sm_syslog(LOG_DEBUG, NOQID,
+ "runqueue %s dowork(%s)",
+ qid_printqueue(w->w_qgrp, w->w_qdir),
+ w->w_name + 2);
+
+ (void) dowork(w->w_qgrp, w->w_qdir, w->w_name + 2,
+ ForkQueueRuns, false, e);
+ errno = 0;
+ }
+ sm_free(w->w_name); /* XXX */
+ if (w->w_host != NULL)
+ sm_free(w->w_host); /* XXX */
+ sm_free((char *) w); /* XXX */
+ sequenceno += seqjump; /* next sequence number */
+#if SM_HEAP_CHECK
+ if (sm_debug_active(&DebugLeakQ, 1))
+ sm_heap_setgroup(oldgroup);
+#endif /* SM_HEAP_CHECK */
+ }
+
+ BlockOldsh = false;
+
+ /* check the signals didn't happen during the revert */
+ if (NoMoreRunners)
+ {
+ /* Check that a valid signal handler is callable */
+ if (Oldsh != SIG_DFL && Oldsh != SIG_IGN &&
+ Oldsh != runners_sighup && Oldsh != runners_sigterm)
+ (*Oldsh)(Oldsig);
+ }
+
+ Oldsh = SIG_DFL; /* after the NoMoreRunners check */
+}
+/*
+** RUN_WORK_GROUP -- run the jobs in a queue group from a work group.
+**
+** Gets the stuff out of the queue in some presumably logical
+** order and processes them.
+**
+** Parameters:
+** wgrp -- work group to process.
+** flags -- RWG_* flags
+**
+** Returns:
+** true if the queue run successfully began.
+**
+** Side Effects:
+** runs things in the mail queue.
+*/
+
+/* Minimum sleep time for persistent queue runners */
+#define MIN_SLEEP_TIME 5
+
+bool
+run_work_group(wgrp, flags)
+ int wgrp;
+ int flags;
+{
+ register ENVELOPE *e;
+ int njobs, qdir;
+ int sequenceno = 1;
+ int qgrp, endgrp, h, i;
+ time_t now;
+ bool full, more;
+ SM_RPOOL_T *rpool;
+ extern ENVELOPE BlankEnvelope;
+ extern SIGFUNC_DECL reapchild __P((int));
+
+ if (wgrp < 0)
+ return false;
+
+ /*
+ ** If no work will ever be selected, don't even bother reading
+ ** the queue.
+ */
+
+ SM_GET_LA(now);
+
+ if (!bitset(RWG_PERSISTENT, flags) &&
+ shouldqueue(WkRecipFact, Current_LA_time))
+ {
+ char *msg = "Skipping queue run -- load average too high";
+
+ if (bitset(RWG_VERBOSE, flags))
+ message("458 %s\n", msg);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, "runqueue: %s", msg);
+ return false;
+ }
+
+ /*
+ ** See if we already have too many children.
+ */
+
+ if (bitset(RWG_FORK, flags) &&
+ WorkGrp[wgrp].wg_lowqintvl > 0 &&
+ !bitset(RWG_PERSISTENT, flags) &&
+ MaxChildren > 0 && CurChildren >= MaxChildren)
+ {
+ char *msg = "Skipping queue run -- too many children";
+
+ if (bitset(RWG_VERBOSE, flags))
+ message("458 %s (%d)\n", msg, CurChildren);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, "runqueue: %s (%d)",
+ msg, CurChildren);
+ return false;
+ }
+
+ /*
+ ** See if we want to go off and do other useful work.
+ */
+
+ if (bitset(RWG_FORK, flags))
+ {
+ pid_t pid;
+
+ (void) sm_blocksignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, reapchild);
+
+ pid = dofork();
+ if (pid == -1)
+ {
+ const char *msg = "Skipping queue run -- fork() failed";
+ const char *err = sm_errstring(errno);
+
+ if (bitset(RWG_VERBOSE, flags))
+ message("458 %s: %s\n", msg, err);
+ if (LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, "runqueue: %s: %s",
+ msg, err);
+ (void) sm_releasesignal(SIGCHLD);
+ return false;
+ }
+ if (pid != 0)
+ {
+ /* parent -- pick up intermediate zombie */
+ (void) sm_blocksignal(SIGALRM);
+
+ /* wgrp only used when queue runners are persistent */
+ proc_list_add(pid, "Queue runner", PROC_QUEUE,
+ WorkGrp[wgrp].wg_maxact,
+ bitset(RWG_PERSISTENT, flags) ? wgrp : -1,
+ NULL);
+ (void) sm_releasesignal(SIGALRM);
+ (void) sm_releasesignal(SIGCHLD);
+ return true;
+ }
+
+ /* child -- clean up signals */
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+ close_sendmail_pid();
+
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ sm_exc_newthread(fatal_error);
+ clrcontrol();
+ proc_list_clear();
+
+ /* Add parent process as first child item */
+ proc_list_add(CurrentPid, "Queue runner child process",
+ PROC_QUEUE_CHILD, 0, -1, NULL);
+ (void) sm_releasesignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ (void) sm_signal(SIGHUP, SIG_DFL);
+ (void) sm_signal(SIGTERM, intsig);
+ }
+
+ /*
+ ** Release any resources used by the daemon code.
+ */
+
+ clrdaemon();
+
+ /* force it to run expensive jobs */
+ NoConnect = false;
+
+ /* drop privileges */
+ if (geteuid() == (uid_t) 0)
+ (void) drop_privileges(false);
+
+ /*
+ ** Create ourselves an envelope
+ */
+
+ CurEnv = &QueueEnvelope;
+ rpool = sm_rpool_new_x(NULL);
+ e = newenvelope(&QueueEnvelope, CurEnv, rpool);
+ e->e_flags = BlankEnvelope.e_flags;
+ e->e_parent = NULL;
+
+ /* make sure we have disconnected from parent */
+ if (bitset(RWG_FORK, flags))
+ {
+ disconnect(1, e);
+ QuickAbort = false;
+ }
+
+ /*
+ ** If we are running part of the queue, always ignore stored
+ ** host status.
+ */
+
+ if (QueueLimitId != NULL || QueueLimitSender != NULL ||
+ QueueLimitQuarantine != NULL ||
+ QueueLimitRecipient != NULL)
+ {
+ IgnoreHostStatus = true;
+ MinQueueAge = 0;
+ }
+
+ /*
+ ** Here is where we choose the queue group from the work group.
+ ** The caller of the "domorework" label must setup a new envelope.
+ */
+
+ endgrp = WorkGrp[wgrp].wg_curqgrp; /* to not spin endlessly */
+
+ domorework:
+
+ /*
+ ** Run a queue group if:
+ ** RWG_RUNALL bit is set or the bit for this group is set.
+ */
+
+ now = curtime();
+ for (;;)
+ {
+ /*
+ ** Find the next queue group within the work group that
+ ** has been marked as needing a run.
+ */
+
+ qgrp = WorkGrp[wgrp].wg_qgs[WorkGrp[wgrp].wg_curqgrp]->qg_index;
+ WorkGrp[wgrp].wg_curqgrp++; /* advance */
+ WorkGrp[wgrp].wg_curqgrp %= WorkGrp[wgrp].wg_numqgrp; /* wrap */
+ if (bitset(RWG_RUNALL, flags) ||
+ (Queue[qgrp]->qg_nextrun <= now &&
+ Queue[qgrp]->qg_nextrun != (time_t) -1))
+ break;
+ if (endgrp == WorkGrp[wgrp].wg_curqgrp)
+ {
+ e->e_id = NULL;
+ if (bitset(RWG_FORK, flags))
+ finis(true, true, ExitStat);
+ return true; /* we're done */
+ }
+ }
+
+ qdir = Queue[qgrp]->qg_curnum; /* round-robin init of queue position */
+#if _FFR_QUEUE_SCHED_DBG
+ if (tTd(69, 12))
+ sm_syslog(LOG_INFO, NOQID,
+ "rwg: wgrp=%d, qgrp=%d, qdir=%d, name=%s, curqgrp=%d, numgrps=%d",
+ wgrp, qgrp, qdir, qid_printqueue(qgrp, qdir),
+ WorkGrp[wgrp].wg_curqgrp, WorkGrp[wgrp].wg_numqgrp);
+#endif /* _FFR_QUEUE_SCHED_DBG */
+
+#if HASNICE
+ /* tweak niceness of queue runs */
+ if (Queue[qgrp]->qg_nice > 0)
+ (void) nice(Queue[qgrp]->qg_nice);
+#endif /* HASNICE */
+
+ /* XXX running queue group... */
+ sm_setproctitle(true, CurEnv, "running queue: %s",
+ qid_printqueue(qgrp, qdir));
+
+ if (LogLevel > 69 || tTd(63, 99))
+ sm_syslog(LOG_DEBUG, NOQID,
+ "runqueue %s, pid=%d, forkflag=%d",
+ qid_printqueue(qgrp, qdir), (int) CurrentPid,
+ bitset(RWG_FORK, flags));
+
+ /*
+ ** 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.
+ */
+
+ for (i = 0; i < Queue[qgrp]->qg_numqueues; i++)
+ {
+ h = gatherq(qgrp, qdir, false, &full, &more);
+#if SM_CONF_SHM
+ if (ShmId != SM_SHM_NO_ID)
+ QSHM_ENTRIES(Queue[qgrp]->qg_qpaths[qdir].qp_idx) = h;
+#endif /* SM_CONF_SHM */
+ /* If there are no more items in this queue advance */
+ if (!more)
+ {
+ /* A round-robin advance */
+ qdir++;
+ qdir %= Queue[qgrp]->qg_numqueues;
+ }
+
+ /* Has the WorkList reached the limit? */
+ if (full)
+ break; /* don't try to gather more */
+ }
+
+ /* order the existing work requests */
+ njobs = sortq(Queue[qgrp]->qg_maxlist);
+ Queue[qgrp]->qg_curnum = qdir; /* update */
+
+
+ if (!Verbose && bitnset(QD_FORK, Queue[qgrp]->qg_flags))
+ {
+ int loop, maxrunners;
+ pid_t pid;
+
+ /*
+ ** For this WorkQ we want to fork off N children (maxrunners)
+ ** at this point. Each child has a copy of WorkQ. Each child
+ ** will process every N-th item. The parent will wait for all
+ ** of the children to finish before moving on to the next
+ ** queue group within the work group. This saves us forking
+ ** a new runner-child for each work item.
+ ** It's valid for qg_maxqrun == 0 since this may be an
+ ** explicit "don't run this queue" setting.
+ */
+
+ maxrunners = Queue[qgrp]->qg_maxqrun;
+
+ /*
+ ** If no runners are configured for this group but
+ ** the queue is "forced" then lets use 1 runner.
+ */
+
+ if (maxrunners == 0 && bitset(RWG_FORCE, flags))
+ maxrunners = 1;
+
+ /* No need to have more runners then there are jobs */
+ if (maxrunners > njobs)
+ maxrunners = njobs;
+ for (loop = 0; loop < maxrunners; loop++)
+ {
+ /*
+ ** Since the delivery may happen in a child and the
+ ** parent does not wait, the parent may close the
+ ** maps thereby removing any shared memory used by
+ ** the map. Therefore, close the maps now so the
+ ** child will dynamically open them if necessary.
+ */
+
+ closemaps(false);
+
+ pid = fork();
+ if (pid < 0)
+ {
+ syserr("run_work_group: cannot fork");
+ return false;
+ }
+ else if (pid > 0)
+ {
+ /* parent -- clean out connection cache */
+ mci_flush(false, NULL);
+#if _FFR_SKIP_DOMAINS
+ if (QueueSortOrder == QSO_BYHOST)
+ {
+ sequenceno += skip_domains(1);
+ }
+ else
+#endif /* _FFR_SKIP_DOMAINS */
+ {
+ /* for the skip */
+ WorkQ = WorkQ->w_next;
+ sequenceno++;
+ }
+ proc_list_add(pid, "Queue child runner process",
+ PROC_QUEUE_CHILD, 0, -1, NULL);
+
+ /* No additional work, no additional runners */
+ if (WorkQ == NULL)
+ break;
+ }
+ else
+ {
+ /* child -- Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+ close_sendmail_pid();
+
+ /*
+ ** Initialize exception stack and default
+ ** exception handler for child process.
+ ** When fork()'d the child now has a private
+ ** copy of WorkQ at its current position.
+ */
+
+ sm_exc_newthread(fatal_error);
+
+ /*
+ ** SMTP processes (whether -bd or -bs) set
+ ** SIGCHLD to reapchild to collect
+ ** children status. However, at delivery
+ ** time, that status must be collected
+ ** by sm_wait() to be dealt with properly
+ ** (check success of delivery based
+ ** on status code, etc). Therefore, if we
+ ** are an SMTP process, reset SIGCHLD
+ ** back to the default so reapchild
+ ** doesn't collect status before
+ ** sm_wait().
+ */
+
+ if (OpMode == MD_SMTP ||
+ OpMode == MD_DAEMON ||
+ MaxQueueChildren > 0)
+ {
+ proc_list_clear();
+ sm_releasesignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ }
+
+ /* child -- error messages to the transcript */
+ QuickAbort = OnlyOneError = false;
+ runner_work(e, sequenceno, true,
+ maxrunners, njobs);
+
+ /* This child is done */
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ }
+ }
+
+ sm_releasesignal(SIGCHLD);
+
+ /*
+ ** Wait until all of the runners have completed before
+ ** seeing if there is another queue group in the
+ ** work group to process.
+ ** XXX Future enhancement: don't wait() for all children
+ ** here, just go ahead and make sure that overall the number
+ ** of children is not exceeded.
+ */
+
+ while (CurChildren > 0)
+ {
+ int status;
+ pid_t ret;
+
+ while ((ret = sm_wait(&status)) <= 0)
+ continue;
+ proc_list_drop(ret, status, NULL);
+ }
+ }
+ else if (Queue[qgrp]->qg_maxqrun > 0 || bitset(RWG_FORCE, flags))
+ {
+ /*
+ ** When current process will not fork children to do the work,
+ ** it will do the work itself. The 'skip' will be 1 since
+ ** there are no child runners to divide the work across.
+ */
+
+ runner_work(e, sequenceno, false, 1, njobs);
+ }
+
+ /* free memory allocated by newenvelope() above */
+ sm_rpool_free(rpool);
+ QueueEnvelope.e_rpool = NULL;
+
+ /* Are there still more queues in the work group to process? */
+ if (endgrp != WorkGrp[wgrp].wg_curqgrp)
+ {
+ rpool = sm_rpool_new_x(NULL);
+ e = newenvelope(&QueueEnvelope, CurEnv, rpool);
+ e->e_flags = BlankEnvelope.e_flags;
+ goto domorework;
+ }
+
+ /* No more queues in work group to process. Now check persistent. */
+ if (bitset(RWG_PERSISTENT, flags))
+ {
+ sequenceno = 1;
+ sm_setproctitle(true, CurEnv, "running queue: %s",
+ qid_printqueue(qgrp, qdir));
+
+ /*
+ ** close bogus maps, i.e., maps which caused a tempfail,
+ ** so we get fresh map connections on the next lookup.
+ ** closemaps() is also called when children are started.
+ */
+
+ closemaps(true);
+
+ /* Close any cached connections. */
+ mci_flush(true, NULL);
+
+ /* Clean out expired related entries. */
+ rmexpstab();
+
+#if NAMED_BIND
+ /* Update MX records for FallbackMX. */
+ if (FallbackMX != NULL)
+ (void) getfallbackmxrr(FallbackMX);
+#endif /* NAMED_BIND */
+
+#if USERDB
+ /* close UserDatabase */
+ _udbx_close();
+#endif /* USERDB */
+
+#if SM_HEAP_CHECK
+ if (sm_debug_active(&SmHeapCheck, 2)
+ && access("memdump", F_OK) == 0
+ )
+ {
+ SM_FILE_T *out;
+
+ remove("memdump");
+ out = sm_io_open(SmFtStdio, SM_TIME_DEFAULT,
+ "memdump.out", SM_IO_APPEND, NULL);
+ if (out != NULL)
+ {
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT, "----------------------\n");
+ sm_heap_report(out,
+ sm_debug_level(&SmHeapCheck) - 1);
+ (void) sm_io_close(out, SM_TIME_DEFAULT);
+ }
+ }
+#endif /* SM_HEAP_CHECK */
+
+ /* let me rest for a second to catch my breath */
+ if (njobs == 0 && WorkGrp[wgrp].wg_lowqintvl < MIN_SLEEP_TIME)
+ sleep(MIN_SLEEP_TIME);
+ else if (WorkGrp[wgrp].wg_lowqintvl <= 0)
+ sleep(QueueIntvl > 0 ? QueueIntvl : MIN_SLEEP_TIME);
+ else
+ sleep(WorkGrp[wgrp].wg_lowqintvl);
+
+ /*
+ ** Get the LA outside the WorkQ loop if necessary.
+ ** In a persistent queue runner the code is repeated over
+ ** and over but gatherq() may ignore entries due to
+ ** shouldqueue() (do we really have to do this twice?).
+ ** Hence the queue runners would just idle around when once
+ ** CurrentLA caused all entries in a queue to be ignored.
+ */
+
+ if (njobs == 0)
+ SM_GET_LA(now);
+ rpool = sm_rpool_new_x(NULL);
+ e = newenvelope(&QueueEnvelope, CurEnv, rpool);
+ e->e_flags = BlankEnvelope.e_flags;
+ goto domorework;
+ }
+
+ /* exit without the usual cleanup */
+ e->e_id = NULL;
+ if (bitset(RWG_FORK, flags))
+ finis(true, true, ExitStat);
+ /* NOTREACHED */
+ return true;
+}
+
+/*
+** DOQUEUERUN -- do a queue run?
+*/
+
+bool
+doqueuerun()
+{
+ return DoQueueRun;
+}
+
+/*
+** RUNQUEUEEVENT -- Sets a flag to indicate that a queue run should be done.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** The invocation of this function via an alarm may interrupt
+** a set of actions. Thus errno may be set in that context.
+** We need to restore errno at the end of this function to ensure
+** that any work done here that sets errno doesn't return a
+** misleading/false errno value. Errno may be EINTR upon entry to
+** this function because of non-restartable/continuable system
+** API was active. Iff this is true we will override errno as
+** a timeout (as a more accurate error message).
+**
+** NOTE: THIS CAN BE CALLED FROM A SIGNAL HANDLER. DO NOT ADD
+** ANYTHING TO THIS ROUTINE UNLESS YOU KNOW WHAT YOU ARE
+** DOING.
+*/
+
+void
+runqueueevent(ignore)
+ int ignore;
+{
+ int save_errno = errno;
+
+ /*
+ ** Set the general bit that we want a queue run,
+ ** tested in doqueuerun()
+ */
+
+ DoQueueRun = true;
+#if _FFR_QUEUE_SCHED_DBG
+ if (tTd(69, 10))
+ sm_syslog(LOG_INFO, NOQID, "rqe: done");
+#endif /* _FFR_QUEUE_SCHED_DBG */
+
+ errno = save_errno;
+ if (errno == EINTR)
+ errno = ETIMEDOUT;
+}
+/*
+** GATHERQ -- gather messages from the message queue(s) the work queue.
+**
+** Parameters:
+** qgrp -- the index of the queue group.
+** qdir -- the index of the queue directory.
+** doall -- if set, include everything in the queue (even
+** the jobs that cannot be run because the load
+** average is too high, or MaxQueueRun is reached).
+** Otherwise, exclude those jobs.
+** full -- (optional) to be set 'true' if WorkList is full
+** more -- (optional) to be set 'true' if there are still more
+** messages in this queue not added to WorkList
+**
+** Returns:
+** The number of request in the queue (not necessarily
+** the number of requests in WorkList however).
+**
+** Side Effects:
+** prepares available work into WorkList
+*/
+
+#define NEED_P 0001 /* 'P': priority */
+#define NEED_T 0002 /* 'T': time */
+#define NEED_R 0004 /* 'R': recipient */
+#define NEED_S 0010 /* 'S': sender */
+#define NEED_H 0020 /* host */
+#define HAS_QUARANTINE 0040 /* has an unexpected 'q' line */
+#define NEED_QUARANTINE 0100 /* 'q': reason */
+
+static WORK *WorkList = NULL; /* list of unsort work */
+static int WorkListSize = 0; /* current max size of WorkList */
+static int WorkListCount = 0; /* # of work items in WorkList */
+
+static int
+gatherq(qgrp, qdir, doall, full, more)
+ int qgrp;
+ int qdir;
+ bool doall;
+ bool *full;
+ bool *more;
+{
+ register struct dirent *d;
+ register WORK *w;
+ register char *p;
+ DIR *f;
+ int i, num_ent;
+ int wn;
+ QUEUE_CHAR *check;
+ char qd[MAXPATHLEN];
+ char qf[MAXPATHLEN];
+
+ wn = WorkListCount - 1;
+ num_ent = 0;
+ if (qdir == NOQDIR)
+ (void) sm_strlcpy(qd, ".", sizeof(qd));
+ else
+ (void) sm_strlcpyn(qd, sizeof(qd), 2,
+ Queue[qgrp]->qg_qpaths[qdir].qp_name,
+ (bitset(QP_SUBQF,
+ Queue[qgrp]->qg_qpaths[qdir].qp_subdirs)
+ ? "/qf" : ""));
+
+ if (tTd(41, 1))
+ {
+ sm_dprintf("gatherq:\n");
+
+ check = QueueLimitId;
+ while (check != NULL)
+ {
+ sm_dprintf("\tQueueLimitId = %s%s\n",
+ check->queue_negate ? "!" : "",
+ check->queue_match);
+ check = check->queue_next;
+ }
+
+ check = QueueLimitSender;
+ while (check != NULL)
+ {
+ sm_dprintf("\tQueueLimitSender = %s%s\n",
+ check->queue_negate ? "!" : "",
+ check->queue_match);
+ check = check->queue_next;
+ }
+
+ check = QueueLimitRecipient;
+ while (check != NULL)
+ {
+ sm_dprintf("\tQueueLimitRecipient = %s%s\n",
+ check->queue_negate ? "!" : "",
+ check->queue_match);
+ check = check->queue_next;
+ }
+
+ if (QueueMode == QM_QUARANTINE)
+ {
+ check = QueueLimitQuarantine;
+ while (check != NULL)
+ {
+ sm_dprintf("\tQueueLimitQuarantine = %s%s\n",
+ check->queue_negate ? "!" : "",
+ check->queue_match);
+ check = check->queue_next;
+ }
+ }
+ }
+
+ /* open the queue directory */
+ f = opendir(qd);
+ if (f == NULL)
+ {
+ syserr("gatherq: cannot open \"%s\"",
+ qid_printqueue(qgrp, qdir));
+ if (full != NULL)
+ *full = WorkListCount >= MaxQueueRun && MaxQueueRun > 0;
+ if (more != NULL)
+ *more = false;
+ return 0;
+ }
+
+ /*
+ ** Read the work directory.
+ */
+
+ while ((d = readdir(f)) != NULL)
+ {
+ SM_FILE_T *cf;
+ int qfver = 0;
+ char lbuf[MAXNAME + 1];
+ struct stat sbuf;
+
+ if (tTd(41, 50))
+ sm_dprintf("gatherq: checking %s..", d->d_name);
+
+ /* is this an interesting entry? */
+ if (!(((QueueMode == QM_NORMAL &&
+ d->d_name[0] == NORMQF_LETTER) ||
+ (QueueMode == QM_QUARANTINE &&
+ d->d_name[0] == QUARQF_LETTER) ||
+ (QueueMode == QM_LOST &&
+ d->d_name[0] == LOSEQF_LETTER)) &&
+ d->d_name[1] == 'f'))
+ {
+ if (tTd(41, 50))
+ sm_dprintf(" skipping\n");
+ continue;
+ }
+ if (tTd(41, 50))
+ sm_dprintf("\n");
+
+ if (strlen(d->d_name) >= MAXQFNAME)
+ {
+ if (Verbose)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "gatherq: %s too long, %d max characters\n",
+ d->d_name, MAXQFNAME);
+ if (LogLevel > 0)
+ sm_syslog(LOG_ALERT, NOQID,
+ "gatherq: %s too long, %d max characters",
+ d->d_name, MAXQFNAME);
+ continue;
+ }
+
+ check = QueueLimitId;
+ while (check != NULL)
+ {
+ if (strcontainedin(false, check->queue_match,
+ d->d_name) != check->queue_negate)
+ break;
+ else
+ check = check->queue_next;
+ }
+ if (QueueLimitId != NULL && check == NULL)
+ continue;
+
+ /* grow work list if necessary */
+ if (++wn >= MaxQueueRun && MaxQueueRun > 0)
+ {
+ if (wn == MaxQueueRun && LogLevel > 0)
+ sm_syslog(LOG_WARNING, NOQID,
+ "WorkList for %s maxed out at %d",
+ qid_printqueue(qgrp, qdir),
+ MaxQueueRun);
+ if (doall)
+ continue; /* just count entries */
+ break;
+ }
+ if (wn >= WorkListSize)
+ {
+ grow_wlist(qgrp, qdir);
+ if (wn >= WorkListSize)
+ continue;
+ }
+ SM_ASSERT(wn >= 0);
+ w = &WorkList[wn];
+
+ (void) sm_strlcpyn(qf, sizeof(qf), 3, qd, "/", d->d_name);
+ if (stat(qf, &sbuf) < 0)
+ {
+ if (errno != ENOENT)
+ sm_syslog(LOG_INFO, NOQID,
+ "gatherq: can't stat %s/%s",
+ qid_printqueue(qgrp, qdir),
+ d->d_name);
+ wn--;
+ continue;
+ }
+ if (!bitset(S_IFREG, sbuf.st_mode))
+ {
+ /* Yikes! Skip it or we will hang on open! */
+ if (!((d->d_name[0] == DATAFL_LETTER ||
+ d->d_name[0] == NORMQF_LETTER ||
+ d->d_name[0] == QUARQF_LETTER ||
+ d->d_name[0] == LOSEQF_LETTER ||
+ d->d_name[0] == XSCRPT_LETTER) &&
+ d->d_name[1] == 'f' && d->d_name[2] == '\0'))
+ syserr("gatherq: %s/%s is not a regular file",
+ qid_printqueue(qgrp, qdir), d->d_name);
+ wn--;
+ continue;
+ }
+
+ /* avoid work if possible */
+ if ((QueueSortOrder == QSO_BYFILENAME ||
+ QueueSortOrder == QSO_BYMODTIME ||
+ QueueSortOrder == QSO_NONE ||
+ QueueSortOrder == QSO_RANDOM) &&
+ QueueLimitQuarantine == NULL &&
+ QueueLimitSender == NULL &&
+ QueueLimitRecipient == NULL)
+ {
+ w->w_qgrp = qgrp;
+ w->w_qdir = qdir;
+ w->w_name = newstr(d->d_name);
+ w->w_host = NULL;
+ w->w_lock = w->w_tooyoung = false;
+ w->w_pri = 0;
+ w->w_ctime = 0;
+ w->w_mtime = sbuf.st_mtime;
+ ++num_ent;
+ continue;
+ }
+
+ /* open control file */
+ cf = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, qf, SM_IO_RDONLY_B,
+ NULL);
+ if (cf == NULL && OpMode != MD_PRINT)
+ {
+ /* this may be some random person sending hir msgs */
+ if (tTd(41, 2))
+ sm_dprintf("gatherq: cannot open %s: %s\n",
+ d->d_name, sm_errstring(errno));
+ errno = 0;
+ wn--;
+ continue;
+ }
+ w->w_qgrp = qgrp;
+ w->w_qdir = qdir;
+ w->w_name = newstr(d->d_name);
+ w->w_host = NULL;
+ if (cf != NULL)
+ {
+ w->w_lock = !lockfile(sm_io_getinfo(cf, SM_IO_WHAT_FD,
+ NULL),
+ w->w_name, NULL,
+ LOCK_SH|LOCK_NB);
+ }
+ w->w_tooyoung = false;
+
+ /* make sure jobs in creation don't clog queue */
+ w->w_pri = 0x7fffffff;
+ w->w_ctime = 0;
+ w->w_mtime = sbuf.st_mtime;
+
+ /* extract useful information */
+ i = NEED_P|NEED_T;
+ if (QueueSortOrder == QSO_BYHOST
+#if _FFR_RHS
+ || QueueSortOrder == QSO_BYSHUFFLE
+#endif /* _FFR_RHS */
+ )
+ {
+ /* need w_host set for host sort order */
+ i |= NEED_H;
+ }
+ if (QueueLimitSender != NULL)
+ i |= NEED_S;
+ if (QueueLimitRecipient != NULL)
+ i |= NEED_R;
+ if (QueueLimitQuarantine != NULL)
+ i |= NEED_QUARANTINE;
+ while (cf != NULL && i != 0 &&
+ sm_io_fgets(cf, SM_TIME_DEFAULT, lbuf,
+ sizeof(lbuf)) != NULL)
+ {
+ int c;
+ time_t age;
+
+ p = strchr(lbuf, '\n');
+ if (p != NULL)
+ *p = '\0';
+ else
+ {
+ /* flush rest of overly long line */
+ while ((c = sm_io_getc(cf, SM_TIME_DEFAULT))
+ != SM_IO_EOF && c != '\n')
+ continue;
+ }
+
+ switch (lbuf[0])
+ {
+ case 'V':
+ qfver = atoi(&lbuf[1]);
+ break;
+
+ case 'P':
+ w->w_pri = atol(&lbuf[1]);
+ i &= ~NEED_P;
+ break;
+
+ case 'T':
+ w->w_ctime = atol(&lbuf[1]);
+ i &= ~NEED_T;
+ break;
+
+ case 'q':
+ if (QueueMode != QM_QUARANTINE &&
+ QueueMode != QM_LOST)
+ {
+ if (tTd(41, 49))
+ sm_dprintf("%s not marked as quarantined but has a 'q' line\n",
+ w->w_name);
+ i |= HAS_QUARANTINE;
+ }
+ else if (QueueMode == QM_QUARANTINE)
+ {
+ if (QueueLimitQuarantine == NULL)
+ {
+ i &= ~NEED_QUARANTINE;
+ break;
+ }
+ p = &lbuf[1];
+ check = QueueLimitQuarantine;
+ while (check != NULL)
+ {
+ if (strcontainedin(false,
+ check->queue_match,
+ p) !=
+ check->queue_negate)
+ break;
+ else
+ check = check->queue_next;
+ }
+ if (check != NULL)
+ i &= ~NEED_QUARANTINE;
+ }
+ break;
+
+ case 'R':
+ if (w->w_host == NULL &&
+ (p = strrchr(&lbuf[1], '@')) != NULL)
+ {
+#if _FFR_RHS
+ if (QueueSortOrder == QSO_BYSHUFFLE)
+ w->w_host = newstr(&p[1]);
+ else
+#endif /* _FFR_RHS */
+ w->w_host = strrev(&p[1]);
+ makelower(w->w_host);
+ i &= ~NEED_H;
+ }
+ if (QueueLimitRecipient == NULL)
+ {
+ i &= ~NEED_R;
+ break;
+ }
+ if (qfver > 0)
+ {
+ p = strchr(&lbuf[1], ':');
+ if (p == NULL)
+ p = &lbuf[1];
+ else
+ ++p; /* skip over ':' */
+ }
+ else
+ p = &lbuf[1];
+ check = QueueLimitRecipient;
+ while (check != NULL)
+ {
+ if (strcontainedin(true,
+ check->queue_match,
+ p) !=
+ check->queue_negate)
+ break;
+ else
+ check = check->queue_next;
+ }
+ if (check != NULL)
+ i &= ~NEED_R;
+ break;
+
+ case 'S':
+ check = QueueLimitSender;
+ while (check != NULL)
+ {
+ if (strcontainedin(true,
+ check->queue_match,
+ &lbuf[1]) !=
+ check->queue_negate)
+ break;
+ else
+ check = check->queue_next;
+ }
+ if (check != NULL)
+ i &= ~NEED_S;
+ break;
+
+ case 'K':
+ age = curtime() - (time_t) atol(&lbuf[1]);
+ if (age >= 0 && MinQueueAge > 0 &&
+ age < MinQueueAge)
+ w->w_tooyoung = true;
+ break;
+
+ case 'N':
+ if (atol(&lbuf[1]) == 0)
+ w->w_tooyoung = false;
+ break;
+ }
+ }
+ if (cf != NULL)
+ (void) sm_io_close(cf, SM_TIME_DEFAULT);
+
+ if ((!doall && (shouldqueue(w->w_pri, w->w_ctime) ||
+ w->w_tooyoung)) ||
+ bitset(HAS_QUARANTINE, i) ||
+ bitset(NEED_QUARANTINE, i) ||
+ bitset(NEED_R|NEED_S, i))
+ {
+ /* don't even bother sorting this job in */
+ if (tTd(41, 49))
+ sm_dprintf("skipping %s (%x)\n", w->w_name, i);
+ sm_free(w->w_name); /* XXX */
+ if (w->w_host != NULL)
+ sm_free(w->w_host); /* XXX */
+ wn--;
+ }
+ else
+ ++num_ent;
+ }
+ (void) closedir(f);
+ wn++;
+
+ i = wn - WorkListCount;
+ WorkListCount += SM_MIN(num_ent, WorkListSize);
+
+ if (more != NULL)
+ *more = WorkListCount < wn;
+
+ if (full != NULL)
+ *full = (wn >= MaxQueueRun && MaxQueueRun > 0) ||
+ (WorkList == NULL && wn > 0);
+
+ return i;
+}
+/*
+** SORTQ -- sort the work list
+**
+** First the old WorkQ is cleared away. Then the WorkList is sorted
+** for all items so that important (higher sorting value) items are not
+** trunctated off. Then the most important items are moved from
+** WorkList to WorkQ. The lower count of 'max' or MaxListCount items
+** are moved.
+**
+** Parameters:
+** max -- maximum number of items to be placed in WorkQ
+**
+** Returns:
+** the number of items in WorkQ
+**
+** Side Effects:
+** WorkQ gets released and filled with new work. WorkList
+** gets released. Work items get sorted in order.
+*/
+
+static int
+sortq(max)
+ int max;
+{
+ register int i; /* local counter */
+ register WORK *w; /* tmp item pointer */
+ int wc = WorkListCount; /* trim size for WorkQ */
+
+ if (WorkQ != NULL)
+ {
+ WORK *nw;
+
+ /* Clear out old WorkQ. */
+ for (w = WorkQ; w != NULL; w = nw)
+ {
+ nw = w->w_next;
+ sm_free(w->w_name); /* XXX */
+ if (w->w_host != NULL)
+ sm_free(w->w_host); /* XXX */
+ sm_free((char *) w); /* XXX */
+ }
+ WorkQ = NULL;
+ }
+
+ if (WorkList == NULL || wc <= 0)
+ return 0;
+
+ /*
+ ** The sort now takes place using all of the items in WorkList.
+ ** The list gets trimmed to the most important items after the sort.
+ ** If the trim were to happen before the sort then one or more
+ ** important items might get truncated off -- not what we want.
+ */
+
+ if (QueueSortOrder == QSO_BYHOST)
+ {
+ /*
+ ** Sort the work directory for the first time,
+ ** based on host name, lock status, and priority.
+ */
+
+ qsort((char *) WorkList, wc, sizeof(*WorkList), workcmpf1);
+
+ /*
+ ** If one message to host is locked, "lock" all messages
+ ** to that host.
+ */
+
+ i = 0;
+ while (i < wc)
+ {
+ if (!WorkList[i].w_lock)
+ {
+ i++;
+ continue;
+ }
+ w = &WorkList[i];
+ while (++i < wc)
+ {
+ if (WorkList[i].w_host == NULL &&
+ w->w_host == NULL)
+ WorkList[i].w_lock = true;
+ else if (WorkList[i].w_host != NULL &&
+ w->w_host != NULL &&
+ sm_strcasecmp(WorkList[i].w_host,
+ w->w_host) == 0)
+ WorkList[i].w_lock = true;
+ else
+ break;
+ }
+ }
+
+ /*
+ ** Sort the work directory for the second time,
+ ** based on lock status, host name, and priority.
+ */
+
+ qsort((char *) WorkList, wc, sizeof(*WorkList), workcmpf2);
+ }
+ else if (QueueSortOrder == QSO_BYTIME)
+ {
+ /*
+ ** Simple sort based on submission time only.
+ */
+
+ qsort((char *) WorkList, wc, sizeof(*WorkList), workcmpf3);
+ }
+ else if (QueueSortOrder == QSO_BYFILENAME)
+ {
+ /*
+ ** Sort based on queue filename.
+ */
+
+ qsort((char *) WorkList, wc, sizeof(*WorkList), workcmpf4);
+ }
+ else if (QueueSortOrder == QSO_RANDOM)
+ {
+ /*
+ ** Sort randomly. To avoid problems with an instable sort,
+ ** use a random index into the queue file name to start
+ ** comparison.
+ */
+
+ randi = get_rand_mod(MAXQFNAME);
+ if (randi < 2)
+ randi = 3;
+ qsort((char *) WorkList, wc, sizeof(*WorkList), workcmpf5);
+ }
+ else if (QueueSortOrder == QSO_BYMODTIME)
+ {
+ /*
+ ** Simple sort based on modification time of queue file.
+ ** This puts the oldest items first.
+ */
+
+ qsort((char *) WorkList, wc, sizeof(*WorkList), workcmpf6);
+ }
+#if _FFR_RHS
+ else if (QueueSortOrder == QSO_BYSHUFFLE)
+ {
+ /*
+ ** Simple sort based on shuffled host name.
+ */
+
+ init_shuffle_alphabet();
+ qsort((char *) WorkList, wc, sizeof(*WorkList), workcmpf7);
+ }
+#endif /* _FFR_RHS */
+ else if (QueueSortOrder == QSO_BYPRIORITY)
+ {
+ /*
+ ** Simple sort based on queue priority only.
+ */
+
+ qsort((char *) WorkList, wc, sizeof(*WorkList), workcmpf0);
+ }
+ /* else don't sort at all */
+
+ /* Check if the per queue group item limit will be exceeded */
+ if (wc > max && max > 0)
+ wc = max;
+
+ /*
+ ** Convert the work list into canonical form.
+ ** Should be turning it into a list of envelopes here perhaps.
+ ** Only take the most important items up to the per queue group
+ ** maximum.
+ */
+
+ for (i = wc; --i >= 0; )
+ {
+ w = (WORK *) xalloc(sizeof(*w));
+ w->w_qgrp = WorkList[i].w_qgrp;
+ w->w_qdir = WorkList[i].w_qdir;
+ w->w_name = WorkList[i].w_name;
+ w->w_host = WorkList[i].w_host;
+ w->w_lock = WorkList[i].w_lock;
+ w->w_tooyoung = WorkList[i].w_tooyoung;
+ w->w_pri = WorkList[i].w_pri;
+ w->w_ctime = WorkList[i].w_ctime;
+ w->w_mtime = WorkList[i].w_mtime;
+ w->w_next = WorkQ;
+ WorkQ = w;
+ }
+
+ /* free the rest of the list */
+ for (i = WorkListCount; --i >= wc; )
+ {
+ sm_free(WorkList[i].w_name);
+ if (WorkList[i].w_host != NULL)
+ sm_free(WorkList[i].w_host);
+ }
+
+ if (WorkList != NULL)
+ sm_free(WorkList); /* XXX */
+ WorkList = NULL;
+ WorkListSize = 0;
+ WorkListCount = 0;
+
+ if (tTd(40, 1))
+ {
+ for (w = WorkQ; w != NULL; w = w->w_next)
+ {
+ if (w->w_host != NULL)
+ sm_dprintf("%22s: pri=%ld %s\n",
+ w->w_name, w->w_pri, w->w_host);
+ else
+ sm_dprintf("%32s: pri=%ld\n",
+ w->w_name, w->w_pri);
+ }
+ }
+
+ return wc; /* return number of WorkQ items */
+}
+/*
+** GROW_WLIST -- make the work list larger
+**
+** Parameters:
+** qgrp -- the index for the queue group.
+** qdir -- the index for the queue directory.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Adds another QUEUESEGSIZE entries to WorkList if possible.
+** It can fail if there isn't enough memory, so WorkListSize
+** should be checked again upon return.
+*/
+
+static void
+grow_wlist(qgrp, qdir)
+ int qgrp;
+ int qdir;
+{
+ if (tTd(41, 1))
+ sm_dprintf("grow_wlist: WorkListSize=%d\n", WorkListSize);
+ if (WorkList == NULL)
+ {
+ WorkList = (WORK *) xalloc((sizeof(*WorkList)) *
+ (QUEUESEGSIZE + 1));
+ WorkListSize = QUEUESEGSIZE;
+ }
+ else
+ {
+ int newsize = WorkListSize + QUEUESEGSIZE;
+ WORK *newlist = (WORK *) sm_realloc((char *) WorkList,
+ (unsigned) sizeof(WORK) * (newsize + 1));
+
+ if (newlist != NULL)
+ {
+ WorkListSize = newsize;
+ WorkList = newlist;
+ if (LogLevel > 1)
+ {
+ sm_syslog(LOG_INFO, NOQID,
+ "grew WorkList for %s to %d",
+ qid_printqueue(qgrp, qdir),
+ WorkListSize);
+ }
+ }
+ else if (LogLevel > 0)
+ {
+ sm_syslog(LOG_ALERT, NOQID,
+ "FAILED to grow WorkList for %s to %d",
+ qid_printqueue(qgrp, qdir), newsize);
+ }
+ }
+ if (tTd(41, 1))
+ sm_dprintf("grow_wlist: WorkListSize now %d\n", WorkListSize);
+}
+/*
+** WORKCMPF0 -- simple priority-only compare function.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** -1 if a < b
+** 0 if a == b
+** +1 if a > b
+**
+*/
+
+static int
+workcmpf0(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ long pa = a->w_pri;
+ long pb = b->w_pri;
+
+ if (pa == pb)
+ return 0;
+ else if (pa > pb)
+ return 1;
+ else
+ return -1;
+}
+/*
+** WORKCMPF1 -- first compare function for ordering work based on host name.
+**
+** Sorts on host name, lock status, and priority in that order.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** <0 if a < b
+** 0 if a == b
+** >0 if a > b
+**
+*/
+
+static int
+workcmpf1(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ int i;
+
+ /* host name */
+ if (a->w_host != NULL && b->w_host == NULL)
+ return 1;
+ else if (a->w_host == NULL && b->w_host != NULL)
+ return -1;
+ if (a->w_host != NULL && b->w_host != NULL &&
+ (i = sm_strcasecmp(a->w_host, b->w_host)) != 0)
+ return i;
+
+ /* lock status */
+ if (a->w_lock != b->w_lock)
+ return b->w_lock - a->w_lock;
+
+ /* job priority */
+ return workcmpf0(a, b);
+}
+/*
+** WORKCMPF2 -- second compare function for ordering work based on host name.
+**
+** Sorts on lock status, host name, and priority in that order.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** <0 if a < b
+** 0 if a == b
+** >0 if a > b
+**
+*/
+
+static int
+workcmpf2(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ int i;
+
+ /* lock status */
+ if (a->w_lock != b->w_lock)
+ return a->w_lock - b->w_lock;
+
+ /* host name */
+ if (a->w_host != NULL && b->w_host == NULL)
+ return 1;
+ else if (a->w_host == NULL && b->w_host != NULL)
+ return -1;
+ if (a->w_host != NULL && b->w_host != NULL &&
+ (i = sm_strcasecmp(a->w_host, b->w_host)) != 0)
+ return i;
+
+ /* job priority */
+ return workcmpf0(a, b);
+}
+/*
+** WORKCMPF3 -- simple submission-time-only compare function.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** -1 if a < b
+** 0 if a == b
+** +1 if a > b
+**
+*/
+
+static int
+workcmpf3(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ if (a->w_ctime > b->w_ctime)
+ return 1;
+ else if (a->w_ctime < b->w_ctime)
+ return -1;
+ else
+ return 0;
+}
+/*
+** WORKCMPF4 -- compare based on file name
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** -1 if a < b
+** 0 if a == b
+** +1 if a > b
+**
+*/
+
+static int
+workcmpf4(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ return strcmp(a->w_name, b->w_name);
+}
+/*
+** WORKCMPF5 -- compare based on assigned random number
+**
+** Parameters:
+** a -- the first argument (ignored).
+** b -- the second argument (ignored).
+**
+** Returns:
+** randomly 1/-1
+*/
+
+/* ARGSUSED0 */
+static int
+workcmpf5(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ if (strlen(a->w_name) < randi || strlen(b->w_name) < randi)
+ return -1;
+ return a->w_name[randi] - b->w_name[randi];
+}
+/*
+** WORKCMPF6 -- simple modification-time-only compare function.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** -1 if a < b
+** 0 if a == b
+** +1 if a > b
+**
+*/
+
+static int
+workcmpf6(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ if (a->w_mtime > b->w_mtime)
+ return 1;
+ else if (a->w_mtime < b->w_mtime)
+ return -1;
+ else
+ return 0;
+}
+#if _FFR_RHS
+/*
+** WORKCMPF7 -- compare function for ordering work based on shuffled host name.
+**
+** Sorts on lock status, host name, and priority in that order.
+**
+** Parameters:
+** a -- the first argument.
+** b -- the second argument.
+**
+** Returns:
+** <0 if a < b
+** 0 if a == b
+** >0 if a > b
+**
+*/
+
+static int
+workcmpf7(a, b)
+ register WORK *a;
+ register WORK *b;
+{
+ int i;
+
+ /* lock status */
+ if (a->w_lock != b->w_lock)
+ return a->w_lock - b->w_lock;
+
+ /* host name */
+ if (a->w_host != NULL && b->w_host == NULL)
+ return 1;
+ else if (a->w_host == NULL && b->w_host != NULL)
+ return -1;
+ if (a->w_host != NULL && b->w_host != NULL &&
+ (i = sm_strshufflecmp(a->w_host, b->w_host)) != 0)
+ return i;
+
+ /* job priority */
+ return workcmpf0(a, b);
+}
+#endif /* _FFR_RHS */
+/*
+** STRREV -- reverse string
+**
+** Returns a pointer to a new string that is the reverse of
+** the string pointed to by fwd. The space for the new
+** string is obtained using xalloc().
+**
+** Parameters:
+** fwd -- the string to reverse.
+**
+** Returns:
+** the reversed string.
+*/
+
+static char *
+strrev(fwd)
+ char *fwd;
+{
+ char *rev = NULL;
+ int len, cnt;
+
+ len = strlen(fwd);
+ rev = xalloc(len + 1);
+ for (cnt = 0; cnt < len; ++cnt)
+ rev[cnt] = fwd[len - cnt - 1];
+ rev[len] = '\0';
+ return rev;
+}
+
+#if _FFR_RHS
+
+# define NASCII 128
+# define NCHAR 256
+
+static unsigned char ShuffledAlphabet[NCHAR];
+
+void
+init_shuffle_alphabet()
+{
+ static bool init = false;
+ int i;
+
+ if (init)
+ return;
+
+ /* fill the ShuffledAlphabet */
+ for (i = 0; i < NASCII; i++)
+ ShuffledAlphabet[i] = i;
+
+ /* mix it */
+ for (i = 1; i < NASCII; i++)
+ {
+ register int j = get_random() % NASCII;
+ register int tmp;
+
+ tmp = ShuffledAlphabet[j];
+ ShuffledAlphabet[j] = ShuffledAlphabet[i];
+ ShuffledAlphabet[i] = tmp;
+ }
+
+ /* make it case insensitive */
+ for (i = 'A'; i <= 'Z'; i++)
+ ShuffledAlphabet[i] = ShuffledAlphabet[i + 'a' - 'A'];
+
+ /* fill the upper part */
+ for (i = 0; i < NASCII; i++)
+ ShuffledAlphabet[i + NASCII] = ShuffledAlphabet[i];
+ init = true;
+}
+
+static int
+sm_strshufflecmp(a, b)
+ char *a;
+ char *b;
+{
+ const unsigned char *us1 = (const unsigned char *) a;
+ const unsigned char *us2 = (const unsigned char *) b;
+
+ while (ShuffledAlphabet[*us1] == ShuffledAlphabet[*us2++])
+ {
+ if (*us1++ == '\0')
+ return 0;
+ }
+ return (ShuffledAlphabet[*us1] - ShuffledAlphabet[*--us2]);
+}
+#endif /* _FFR_RHS */
+
+/*
+** DOWORK -- do a work request.
+**
+** Parameters:
+** qgrp -- the index of the queue group for the job.
+** qdir -- the index of the queue directory for the job.
+** id -- the ID of the job to run.
+** forkflag -- if set, run this in background.
+** requeueflag -- if set, reinstantiate the queue quickly.
+** This is used when expanding aliases in the queue.
+** If forkflag is also set, it doesn't wait for the
+** child.
+** e - the envelope in which to run it.
+**
+** Returns:
+** process id of process that is running the queue job.
+**
+** Side Effects:
+** The work request is satisfied if possible.
+*/
+
+pid_t
+dowork(qgrp, qdir, id, forkflag, requeueflag, e)
+ int qgrp;
+ int qdir;
+ char *id;
+ bool forkflag;
+ bool requeueflag;
+ register ENVELOPE *e;
+{
+ register pid_t pid;
+ SM_RPOOL_T *rpool;
+
+ if (tTd(40, 1))
+ sm_dprintf("dowork(%s/%s)\n", qid_printqueue(qgrp, qdir), id);
+
+ /*
+ ** Fork for work.
+ */
+
+ if (forkflag)
+ {
+ /*
+ ** Since the delivery may happen in a child and the
+ ** parent does not wait, the parent may close the
+ ** maps thereby removing any shared memory used by
+ ** the map. Therefore, close the maps now so the
+ ** child will dynamically open them if necessary.
+ */
+
+ closemaps(false);
+
+ pid = fork();
+ if (pid < 0)
+ {
+ syserr("dowork: cannot fork");
+ return 0;
+ }
+ else if (pid > 0)
+ {
+ /* parent -- clean out connection cache */
+ mci_flush(false, NULL);
+ }
+ else
+ {
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+ sm_exc_newthread(fatal_error);
+
+ /*
+ ** See note above about SMTP processes and SIGCHLD.
+ */
+
+ if (OpMode == MD_SMTP ||
+ OpMode == MD_DAEMON ||
+ MaxQueueChildren > 0)
+ {
+ proc_list_clear();
+ sm_releasesignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ }
+
+ /* child -- error messages to the transcript */
+ QuickAbort = OnlyOneError = false;
+ }
+ }
+ else
+ {
+ pid = 0;
+ }
+
+ if (pid == 0)
+ {
+ /*
+ ** CHILD
+ ** 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.
+ */
+
+ if (forkflag)
+ {
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ }
+
+ /* set basic modes, etc. */
+ sm_clear_events();
+ clearstats();
+ rpool = sm_rpool_new_x(NULL);
+ clearenvelope(e, false, rpool);
+ e->e_flags |= EF_QUEUERUN|EF_GLOBALERRS;
+ set_delivery_mode(SM_DELIVER, e);
+ e->e_errormode = EM_MAIL;
+ e->e_id = id;
+ e->e_qgrp = qgrp;
+ e->e_qdir = qdir;
+ GrabTo = UseErrorsTo = false;
+ ExitStat = EX_OK;
+ if (forkflag)
+ {
+ disconnect(1, e);
+ set_op_mode(MD_QUEUERUN);
+ }
+ sm_setproctitle(true, e, "%s from queue", qid_printname(e));
+ if (LogLevel > 76)
+ sm_syslog(LOG_DEBUG, e->e_id, "dowork, pid=%d",
+ (int) CurrentPid);
+
+ /* don't use the headers from sendmail.cf... */
+ e->e_header = NULL;
+
+ /* read the queue control file -- return if locked */
+ if (!readqf(e, false))
+ {
+ if (tTd(40, 4) && e->e_id != NULL)
+ sm_dprintf("readqf(%s) failed\n",
+ qid_printname(e));
+ e->e_id = NULL;
+ if (forkflag)
+ finis(false, true, EX_OK);
+ else
+ {
+ /* adding this frees 8 bytes */
+ clearenvelope(e, false, rpool);
+
+ /* adding this frees 12 bytes */
+ sm_rpool_free(rpool);
+ e->e_rpool = NULL;
+ return 0;
+ }
+ }
+
+ e->e_flags |= EF_INQUEUE;
+ eatheader(e, requeueflag, true);
+
+ if (requeueflag)
+ queueup(e, false, false);
+
+ /* do the delivery */
+ sendall(e, SM_DELIVER);
+
+ /* finish up and exit */
+ if (forkflag)
+ finis(true, true, ExitStat);
+ else
+ {
+ dropenvelope(e, true, false);
+ sm_rpool_free(rpool);
+ e->e_rpool = NULL;
+ }
+ }
+ e->e_id = NULL;
+ return pid;
+}
+
+/*
+** DOWORKLIST -- process a list of envelopes as work requests
+**
+** Similar to dowork(), except that after forking, it processes an
+** envelope and its siblings, treating each envelope as a work request.
+**
+** Parameters:
+** el -- envelope to be processed including its siblings.
+** forkflag -- if set, run this in background.
+** requeueflag -- if set, reinstantiate the queue quickly.
+** This is used when expanding aliases in the queue.
+** If forkflag is also set, it doesn't wait for the
+** child.
+**
+** Returns:
+** process id of process that is running the queue job.
+**
+** Side Effects:
+** The work request is satisfied if possible.
+*/
+
+pid_t
+doworklist(el, forkflag, requeueflag)
+ ENVELOPE *el;
+ bool forkflag;
+ bool requeueflag;
+{
+ register pid_t pid;
+ ENVELOPE *ei;
+
+ if (tTd(40, 1))
+ sm_dprintf("doworklist()\n");
+
+ /*
+ ** Fork for work.
+ */
+
+ if (forkflag)
+ {
+ /*
+ ** Since the delivery may happen in a child and the
+ ** parent does not wait, the parent may close the
+ ** maps thereby removing any shared memory used by
+ ** the map. Therefore, close the maps now so the
+ ** child will dynamically open them if necessary.
+ */
+
+ closemaps(false);
+
+ pid = fork();
+ if (pid < 0)
+ {
+ syserr("doworklist: cannot fork");
+ return 0;
+ }
+ else if (pid > 0)
+ {
+ /* parent -- clean out connection cache */
+ mci_flush(false, NULL);
+ }
+ else
+ {
+ /*
+ ** Initialize exception stack and default exception
+ ** handler for child process.
+ */
+
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ CurrentPid = getpid();
+ sm_exc_newthread(fatal_error);
+
+ /*
+ ** See note above about SMTP processes and SIGCHLD.
+ */
+
+ if (OpMode == MD_SMTP ||
+ OpMode == MD_DAEMON ||
+ MaxQueueChildren > 0)
+ {
+ proc_list_clear();
+ sm_releasesignal(SIGCHLD);
+ (void) sm_signal(SIGCHLD, SIG_DFL);
+ }
+
+ /* child -- error messages to the transcript */
+ QuickAbort = OnlyOneError = false;
+ }
+ }
+ else
+ {
+ pid = 0;
+ }
+
+ if (pid != 0)
+ return pid;
+
+ /*
+ ** IN CHILD
+ ** 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.
+ */
+
+ if (forkflag)
+ {
+ /* Reset global flags */
+ RestartRequest = NULL;
+ RestartWorkGroup = false;
+ ShutdownRequest = NULL;
+ PendingSignal = 0;
+ }
+
+ /* set basic modes, etc. */
+ sm_clear_events();
+ clearstats();
+ GrabTo = UseErrorsTo = false;
+ ExitStat = EX_OK;
+ if (forkflag)
+ {
+ disconnect(1, el);
+ set_op_mode(MD_QUEUERUN);
+ }
+ if (LogLevel > 76)
+ sm_syslog(LOG_DEBUG, el->e_id, "doworklist, pid=%d",
+ (int) CurrentPid);
+
+ for (ei = el; ei != NULL; ei = ei->e_sibling)
+ {
+ ENVELOPE e;
+ SM_RPOOL_T *rpool;
+
+ if (WILL_BE_QUEUED(ei->e_sendmode))
+ continue;
+ else if (QueueMode != QM_QUARANTINE &&
+ ei->e_quarmsg != NULL)
+ continue;
+
+ rpool = sm_rpool_new_x(NULL);
+ clearenvelope(&e, true, rpool);
+ e.e_flags |= EF_QUEUERUN|EF_GLOBALERRS;
+ set_delivery_mode(SM_DELIVER, &e);
+ e.e_errormode = EM_MAIL;
+ e.e_id = ei->e_id;
+ e.e_qgrp = ei->e_qgrp;
+ e.e_qdir = ei->e_qdir;
+ openxscript(&e);
+ sm_setproctitle(true, &e, "%s from queue", qid_printname(&e));
+
+ /* don't use the headers from sendmail.cf... */
+ e.e_header = NULL;
+ CurEnv = &e;
+
+ /* read the queue control file -- return if locked */
+ if (readqf(&e, false))
+ {
+ e.e_flags |= EF_INQUEUE;
+ eatheader(&e, requeueflag, true);
+
+ if (requeueflag)
+ queueup(&e, false, false);
+
+ /* do the delivery */
+ sendall(&e, SM_DELIVER);
+ dropenvelope(&e, true, false);
+ }
+ else
+ {
+ if (tTd(40, 4) && e.e_id != NULL)
+ sm_dprintf("readqf(%s) failed\n",
+ qid_printname(&e));
+ }
+ sm_rpool_free(rpool);
+ ei->e_id = NULL;
+ }
+
+ /* restore CurEnv */
+ CurEnv = el;
+
+ /* finish up and exit */
+ if (forkflag)
+ finis(true, true, ExitStat);
+ return 0;
+}
+/*
+** READQF -- read queue file and set up environment.
+**
+** Parameters:
+** e -- the envelope of the job to run.
+** openonly -- only open the qf (returned as e_lockfp)
+**
+** Returns:
+** true if it successfully read the queue file.
+** false otherwise.
+**
+** Side Effects:
+** The queue file is returned locked.
+*/
+
+static bool
+readqf(e, openonly)
+ register ENVELOPE *e;
+ bool openonly;
+{
+ register SM_FILE_T *qfp;
+ ADDRESS *ctladdr;
+ struct stat st, stf;
+ char *bp;
+ int qfver = 0;
+ long hdrsize = 0;
+ register char *p;
+ char *frcpt = NULL;
+ char *orcpt = NULL;
+ bool nomore = false;
+ bool bogus = false;
+ MODE_T qsafe;
+ char *err;
+ char qf[MAXPATHLEN];
+ char buf[MAXLINE];
+ int bufsize;
+
+ /*
+ ** Read and process the file.
+ */
+
+ SM_REQUIRE(e != NULL);
+ bp = NULL;
+ (void) sm_strlcpy(qf, queuename(e, ANYQFL_LETTER), sizeof(qf));
+ qfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, qf, SM_IO_RDWR_B, NULL);
+ if (qfp == NULL)
+ {
+ int save_errno = errno;
+
+ if (tTd(40, 8))
+ sm_dprintf("readqf(%s): sm_io_open failure (%s)\n",
+ qf, sm_errstring(errno));
+ errno = save_errno;
+ if (errno != ENOENT
+ )
+ syserr("readqf: no control file %s", qf);
+ RELEASE_QUEUE;
+ return false;
+ }
+
+ if (!lockfile(sm_io_getinfo(qfp, SM_IO_WHAT_FD, NULL), qf, NULL,
+ LOCK_EX|LOCK_NB))
+ {
+ /* being processed by another queuer */
+ if (Verbose)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%s: locked\n", e->e_id);
+ if (tTd(40, 8))
+ sm_dprintf("%s: locked\n", e->e_id);
+ if (LogLevel > 19)
+ sm_syslog(LOG_DEBUG, e->e_id, "locked");
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ RELEASE_QUEUE;
+ return false;
+ }
+
+ RELEASE_QUEUE;
+
+ /*
+ ** Prevent locking race condition.
+ **
+ ** Process A: readqf(): qfp = fopen(qffile)
+ ** Process B: queueup(): rename(tf, qf)
+ ** Process B: unlocks(tf)
+ ** Process A: lockfile(qf);
+ **
+ ** Process A (us) has the old qf file (before the rename deleted
+ ** the directory entry) and will be delivering based on old data.
+ ** This can lead to multiple deliveries of the same recipients.
+ **
+ ** Catch this by checking if the underlying qf file has changed
+ ** *after* acquiring our lock and if so, act as though the file
+ ** was still locked (i.e., just return like the lockfile() case
+ ** above.
+ */
+
+ if (stat(qf, &stf) < 0 ||
+ fstat(sm_io_getinfo(qfp, SM_IO_WHAT_FD, NULL), &st) < 0)
+ {
+ /* must have been being processed by someone else */
+ if (tTd(40, 8))
+ sm_dprintf("readqf(%s): [f]stat failure (%s)\n",
+ qf, sm_errstring(errno));
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+ if (st.st_nlink != stf.st_nlink ||
+ st.st_dev != stf.st_dev ||
+ ST_INODE(st) != ST_INODE(stf) ||
+#if HAS_ST_GEN && 0 /* AFS returns garbage in st_gen */
+ st.st_gen != stf.st_gen ||
+#endif /* HAS_ST_GEN && 0 */
+ st.st_uid != stf.st_uid ||
+ st.st_gid != stf.st_gid ||
+ st.st_size != stf.st_size)
+ {
+ /* changed after opened */
+ if (Verbose)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%s: changed\n", e->e_id);
+ if (tTd(40, 8))
+ sm_dprintf("%s: changed\n", e->e_id);
+ if (LogLevel > 19)
+ sm_syslog(LOG_DEBUG, e->e_id, "changed");
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+ /*
+ ** Check the queue file for plausibility to avoid attacks.
+ */
+
+ qsafe = S_IWOTH|S_IWGRP;
+ if (bitset(S_IWGRP, QueueFileMode))
+ qsafe &= ~S_IWGRP;
+
+ bogus = st.st_uid != geteuid() &&
+ st.st_uid != TrustedUid &&
+ geteuid() != RealUid;
+
+ /*
+ ** If this qf file results from a set-group-ID binary, then
+ ** we check whether the directory is group-writable,
+ ** the queue file mode contains the group-writable bit, and
+ ** the groups are the same.
+ ** Notice: this requires that the set-group-ID binary is used to
+ ** run the queue!
+ */
+
+ if (bogus && st.st_gid == getegid() && UseMSP)
+ {
+ char delim;
+ struct stat dst;
+
+ bp = SM_LAST_DIR_DELIM(qf);
+ if (bp == NULL)
+ delim = '\0';
+ else
+ {
+ delim = *bp;
+ *bp = '\0';
+ }
+ if (stat(delim == '\0' ? "." : qf, &dst) < 0)
+ syserr("readqf: cannot stat directory %s",
+ delim == '\0' ? "." : qf);
+ else
+ {
+ bogus = !(bitset(S_IWGRP, QueueFileMode) &&
+ bitset(S_IWGRP, dst.st_mode) &&
+ dst.st_gid == st.st_gid);
+ }
+ if (delim != '\0')
+ *bp = delim;
+ bp = NULL;
+ }
+ if (!bogus)
+ bogus = bitset(qsafe, st.st_mode);
+ if (bogus)
+ {
+ if (LogLevel > 0)
+ {
+ sm_syslog(LOG_ALERT, e->e_id,
+ "bogus queue file, uid=%d, gid=%d, mode=%o",
+ st.st_uid, st.st_gid, st.st_mode);
+ }
+ if (tTd(40, 8))
+ sm_dprintf("readqf(%s): bogus file\n", qf);
+ e->e_flags |= EF_INQUEUE;
+ if (!openonly)
+ loseqfile(e, "bogus file uid/gid in mqueue");
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+ if (st.st_size == 0)
+ {
+ /* must be a bogus file -- if also old, just remove it */
+ if (!openonly && st.st_ctime + 10 * 60 < curtime())
+ {
+ (void) xunlink(queuename(e, DATAFL_LETTER));
+ (void) xunlink(queuename(e, ANYQFL_LETTER));
+ }
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+ if (st.st_nlink == 0)
+ {
+ /*
+ ** Race condition -- we got a file just as it was being
+ ** unlinked. Just assume it is zero length.
+ */
+
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+#if _FFR_TRUSTED_QF
+ /*
+ ** If we don't own the file mark it as unsafe.
+ ** However, allow TrustedUser to own it as well
+ ** in case TrustedUser manipulates the queue.
+ */
+
+ if (st.st_uid != geteuid() && st.st_uid != TrustedUid)
+ e->e_flags |= EF_UNSAFE;
+#else /* _FFR_TRUSTED_QF */
+ /* If we don't own the file mark it as unsafe */
+ if (st.st_uid != geteuid())
+ e->e_flags |= EF_UNSAFE;
+#endif /* _FFR_TRUSTED_QF */
+
+ /* good file -- save this lock */
+ e->e_lockfp = qfp;
+
+ /* Just wanted the open file */
+ if (openonly)
+ return true;
+
+ /* do basic system initialization */
+ initsys(e);
+ macdefine(&e->e_macro, A_PERM, 'i', e->e_id);
+
+ LineNumber = 0;
+ e->e_flags |= EF_GLOBALERRS;
+ set_op_mode(MD_QUEUERUN);
+ ctladdr = NULL;
+ e->e_qfletter = queue_letter(e, ANYQFL_LETTER);
+ e->e_dfqgrp = e->e_qgrp;
+ e->e_dfqdir = e->e_qdir;
+#if _FFR_QUEUE_MACRO
+ macdefine(&e->e_macro, A_TEMP, macid("{queue}"),
+ qid_printqueue(e->e_qgrp, e->e_qdir));
+#endif /* _FFR_QUEUE_MACRO */
+ e->e_dfino = -1;
+ e->e_msgsize = -1;
+ while (bufsize = sizeof(buf),
+ (bp = fgetfolded(buf, &bufsize, qfp)) != NULL)
+ {
+ unsigned long qflags;
+ ADDRESS *q;
+ int r;
+ time_t now;
+ auto char *ep;
+
+ if (tTd(40, 4))
+ sm_dprintf("+++++ %s\n", bp);
+ if (nomore)
+ {
+ /* hack attack */
+ hackattack:
+ syserr("SECURITY ALERT: extra or bogus data in queue file: %s",
+ bp);
+ err = "bogus queue line";
+ goto fail;
+ }
+ switch (bp[0])
+ {
+ case 'A': /* AUTH= parameter */
+ if (!xtextok(&bp[1]))
+ goto hackattack;
+ e->e_auth_param = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ break;
+
+ case 'B': /* body type */
+ r = check_bodytype(&bp[1]);
+ if (!BODYTYPE_VALID(r))
+ goto hackattack;
+ e->e_bodytype = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ break;
+
+ case 'C': /* specify controlling user */
+ ctladdr = setctluser(&bp[1], qfver, e);
+ break;
+
+ case 'D': /* data file name */
+ /* obsolete -- ignore */
+ break;
+
+ case 'd': /* data file directory name */
+ {
+ int qgrp, qdir;
+
+#if _FFR_MSP_PARANOIA
+ /* forbid queue groups in MSP? */
+ if (UseMSP)
+ goto hackattack;
+#endif /* _FFR_MSP_PARANOIA */
+ for (qgrp = 0;
+ qgrp < NumQueue && Queue[qgrp] != NULL;
+ ++qgrp)
+ {
+ for (qdir = 0;
+ qdir < Queue[qgrp]->qg_numqueues;
+ ++qdir)
+ {
+ if (strcmp(&bp[1],
+ Queue[qgrp]->qg_qpaths[qdir].qp_name)
+ == 0)
+ {
+ e->e_dfqgrp = qgrp;
+ e->e_dfqdir = qdir;
+ goto done;
+ }
+ }
+ }
+ err = "bogus queue file directory";
+ goto fail;
+ done:
+ break;
+ }
+
+ case 'E': /* specify error recipient */
+ /* no longer used */
+ break;
+
+ case 'F': /* flag bits */
+ if (strncmp(bp, "From ", 5) == 0)
+ {
+ /* we are being spoofed! */
+ syserr("SECURITY ALERT: bogus qf line %s", bp);
+ err = "bogus queue line";
+ goto fail;
+ }
+ for (p = &bp[1]; *p != '\0'; p++)
+ {
+ switch (*p)
+ {
+ case '8': /* has 8 bit data */
+ e->e_flags |= EF_HAS8BIT;
+ break;
+
+ case 'b': /* delete Bcc: header */
+ e->e_flags |= EF_DELETE_BCC;
+ break;
+
+ case 'd': /* envelope has DSN RET= */
+ e->e_flags |= EF_RET_PARAM;
+ break;
+
+ case 'n': /* don't return body */
+ e->e_flags |= EF_NO_BODY_RETN;
+ break;
+
+ case 'r': /* response */
+ e->e_flags |= EF_RESPONSE;
+ break;
+
+ case 's': /* split */
+ e->e_flags |= EF_SPLIT;
+ break;
+
+ case 'w': /* warning sent */
+ e->e_flags |= EF_WARNING;
+ break;
+ }
+ }
+ break;
+
+ case 'q': /* quarantine reason */
+ e->e_quarmsg = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{quarantine}"), e->e_quarmsg);
+ break;
+
+ case 'H': /* header */
+
+ /*
+ ** count size before chompheader() destroys the line.
+ ** this isn't accurate due to macro expansion, but
+ ** better than before. "-3" to skip H?? at least.
+ */
+
+ hdrsize += strlen(bp) - 3;
+ (void) chompheader(&bp[1], CHHDR_QUEUE, NULL, e);
+ break;
+
+ case 'I': /* data file's inode number */
+ /* regenerated below */
+ break;
+
+ case 'K': /* time of last delivery attempt */
+ e->e_dtime = atol(&buf[1]);
+ break;
+
+ case 'L': /* Solaris Content-Length: */
+ case 'M': /* message */
+ /* ignore this; we want a new message next time */
+ break;
+
+ case 'N': /* number of delivery attempts */
+ e->e_ntries = atoi(&buf[1]);
+
+ /* if this has been tried recently, let it be */
+ now = curtime();
+ if (e->e_ntries > 0 && e->e_dtime <= now &&
+ now < e->e_dtime + MinQueueAge)
+ {
+ char *howlong;
+
+ howlong = pintvl(now - e->e_dtime, true);
+ if (Verbose)
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%s: too young (%s)\n",
+ e->e_id, howlong);
+ if (tTd(40, 8))
+ sm_dprintf("%s: too young (%s)\n",
+ e->e_id, howlong);
+ if (LogLevel > 19)
+ sm_syslog(LOG_DEBUG, e->e_id,
+ "too young (%s)",
+ howlong);
+ e->e_id = NULL;
+ unlockqueue(e);
+ if (bp != buf)
+ sm_free(bp);
+ return false;
+ }
+ macdefine(&e->e_macro, A_TEMP,
+ macid("{ntries}"), &buf[1]);
+
+#if NAMED_BIND
+ /* adjust BIND parameters immediately */
+ if (e->e_ntries == 0)
+ {
+ _res.retry = TimeOuts.res_retry[RES_TO_FIRST];
+ _res.retrans = TimeOuts.res_retrans[RES_TO_FIRST];
+ }
+ else
+ {
+ _res.retry = TimeOuts.res_retry[RES_TO_NORMAL];
+ _res.retrans = TimeOuts.res_retrans[RES_TO_NORMAL];
+ }
+#endif /* NAMED_BIND */
+ break;
+
+ case 'P': /* message priority */
+ e->e_msgpriority = atol(&bp[1]) + WkTimeFact;
+ break;
+
+ case 'Q': /* original recipient */
+ orcpt = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ break;
+
+ case 'r': /* final recipient */
+ frcpt = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ break;
+
+ case 'R': /* specify recipient */
+ p = bp;
+ qflags = 0;
+ if (qfver >= 1)
+ {
+ /* get flag bits */
+ while (*++p != '\0' && *p != ':')
+ {
+ switch (*p)
+ {
+ case 'N':
+ qflags |= QHASNOTIFY;
+ break;
+
+ case 'S':
+ qflags |= QPINGONSUCCESS;
+ break;
+
+ case 'F':
+ qflags |= QPINGONFAILURE;
+ break;
+
+ case 'D':
+ qflags |= QPINGONDELAY;
+ break;
+
+ case 'P':
+ qflags |= QPRIMARY;
+ break;
+
+ case 'A':
+ if (ctladdr != NULL)
+ ctladdr->q_flags |= QALIAS;
+ break;
+
+ default: /* ignore or complain? */
+ break;
+ }
+ }
+ }
+ else
+ qflags |= QPRIMARY;
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"),
+ "e r");
+ if (*p != '\0')
+ q = parseaddr(++p, NULLADDR, RF_COPYALL, '\0',
+ NULL, e, true);
+ else
+ q = NULL;
+ if (q != NULL)
+ {
+ /* make sure we keep the current qgrp */
+ if (ISVALIDQGRP(e->e_qgrp))
+ q->q_qgrp = e->e_qgrp;
+ q->q_alias = ctladdr;
+ if (qfver >= 1)
+ q->q_flags &= ~Q_PINGFLAGS;
+ q->q_flags |= qflags;
+ q->q_finalrcpt = frcpt;
+ q->q_orcpt = orcpt;
+ (void) recipient(q, &e->e_sendqueue, 0, e);
+ }
+ frcpt = NULL;
+ orcpt = NULL;
+ macdefine(&e->e_macro, A_PERM, macid("{addr_type}"),
+ NULL);
+ break;
+
+ case 'S': /* sender */
+ setsender(sm_rpool_strdup_x(e->e_rpool, &bp[1]),
+ e, NULL, '\0', true);
+ break;
+
+ case 'T': /* init time */
+ e->e_ctime = atol(&bp[1]);
+ break;
+
+ case 'V': /* queue file version number */
+ qfver = atoi(&bp[1]);
+ if (qfver <= QF_VERSION)
+ break;
+ syserr("Version number in queue file (%d) greater than max (%d)",
+ qfver, QF_VERSION);
+ err = "unsupported queue file version";
+ goto fail;
+ /* NOTREACHED */
+ break;
+
+ case 'Z': /* original envelope id from ESMTP */
+ e->e_envid = sm_rpool_strdup_x(e->e_rpool, &bp[1]);
+ macdefine(&e->e_macro, A_PERM,
+ macid("{dsn_envid}"), e->e_envid);
+ break;
+
+ case '!': /* deliver by */
+
+ /* format: flag (1 char) space long-integer */
+ e->e_dlvr_flag = buf[1];
+ e->e_deliver_by = strtol(&buf[3], NULL, 10);
+
+ case '$': /* define macro */
+ {
+ char *p;
+
+ /* XXX elimate p? */
+ r = macid_parse(&bp[1], &ep);
+ if (r == 0)
+ break;
+ p = sm_rpool_strdup_x(e->e_rpool, ep);
+ macdefine(&e->e_macro, A_PERM, r, p);
+ }
+ break;
+
+ case '.': /* terminate file */
+ nomore = true;
+ break;
+
+#if _FFR_QUEUEDELAY
+ case 'G':
+ case 'Y':
+
+ /*
+ ** Maintain backward compatibility for
+ ** users who defined _FFR_QUEUEDELAY in
+ ** previous releases. Remove this
+ ** code in 8.14 or 8.15.
+ */
+
+ if (qfver == 5 || qfver == 7)
+ break;
+
+ /* If not qfver 5 or 7, then 'G' or 'Y' is invalid */
+ /* FALLTHROUGH */
+#endif /* _FFR_QUEUEDELAY */
+
+ default:
+ syserr("readqf: %s: line %d: bad line \"%s\"",
+ qf, LineNumber, shortenstring(bp, MAXSHORTSTR));
+ err = "unrecognized line";
+ goto fail;
+ }
+
+ if (bp != buf)
+ SM_FREE(bp);
+ }
+
+ /*
+ ** 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 true;
+ }
+
+ /* Check to make sure we have a complete queue file read */
+ if (!nomore)
+ {
+ syserr("readqf: %s: incomplete queue file read", qf);
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+#if _FFR_QF_PARANOIA
+ /* Check to make sure key fields were read */
+ if (e->e_from.q_mailer == NULL)
+ {
+ syserr("readqf: %s: sender not specified in queue file", qf);
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ return false;
+ }
+ /* other checks? */
+#endif /* _FFR_QF_PARANOIA */
+
+ /* possibly set ${dsn_ret} macro */
+ if (bitset(EF_RET_PARAM, e->e_flags))
+ {
+ if (bitset(EF_NO_BODY_RETN, e->e_flags))
+ macdefine(&e->e_macro, A_PERM,
+ macid("{dsn_ret}"), "hdrs");
+ else
+ macdefine(&e->e_macro, A_PERM,
+ macid("{dsn_ret}"), "full");
+ }
+
+ /*
+ ** Arrange to read the data file.
+ */
+
+ p = queuename(e, DATAFL_LETTER);
+ e->e_dfp = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, p, SM_IO_RDONLY_B,
+ NULL);
+ if (e->e_dfp == NULL)
+ {
+ syserr("readqf: cannot open %s", p);
+ }
+ else
+ {
+ e->e_flags |= EF_HAS_DF;
+ if (fstat(sm_io_getinfo(e->e_dfp, SM_IO_WHAT_FD, NULL), &st)
+ >= 0)
+ {
+ e->e_msgsize = st.st_size + hdrsize;
+ e->e_dfdev = st.st_dev;
+ e->e_dfino = ST_INODE(st);
+ (void) sm_snprintf(buf, sizeof(buf), "%ld",
+ e->e_msgsize);
+ macdefine(&e->e_macro, A_TEMP, macid("{msg_size}"),
+ buf);
+ }
+ }
+
+ return true;
+
+ fail:
+ /*
+ ** There was some error reading the qf file (reason is in err var.)
+ ** Cleanup:
+ ** close file; clear e_lockfp since it is the same as qfp,
+ ** hence it is invalid (as file) after qfp is closed;
+ ** the qf file is on disk, so set the flag to avoid calling
+ ** queueup() with bogus data.
+ */
+
+ if (bp != buf)
+ SM_FREE(bp);
+ if (qfp != NULL)
+ (void) sm_io_close(qfp, SM_TIME_DEFAULT);
+ e->e_lockfp = NULL;
+ e->e_flags |= EF_INQUEUE;
+ loseqfile(e, err);
+ return false;
+}
+/*
+** PRTSTR -- print a string, "unprintable" characters are shown as \oct
+**
+** Parameters:
+** s -- string to print
+** ml -- maximum length of output
+**
+** Returns:
+** number of entries
+**
+** Side Effects:
+** Prints a string on stdout.
+*/
+
+static void prtstr __P((char *, int));
+
+static void
+prtstr(s, ml)
+ char *s;
+ int ml;
+{
+ int c;
+
+ if (s == NULL)
+ return;
+ while (ml-- > 0 && ((c = *s++) != '\0'))
+ {
+ if (c == '\\')
+ {
+ if (ml-- > 0)
+ {
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, c);
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, c);
+ }
+ }
+ else if (isascii(c) && isprint(c))
+ (void) sm_io_putc(smioout, SM_TIME_DEFAULT, c);
+ else
+ {
+ if ((ml -= 3) > 0)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\\%03o", c & 0xFF);
+ }
+ }
+}
+/*
+** PRINTNQE -- print out number of entries in the mail queue
+**
+** Parameters:
+** out -- output file pointer.
+** prefix -- string to output in front of each line.
+**
+** Returns:
+** none.
+*/
+
+void
+printnqe(out, prefix)
+ SM_FILE_T *out;
+ char *prefix;
+{
+#if SM_CONF_SHM
+ int i, k = 0, nrequests = 0;
+ bool unknown = false;
+
+ if (ShmId == SM_SHM_NO_ID)
+ {
+ if (prefix == NULL)
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "Data unavailable: shared memory not updated\n");
+ else
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%sNOTCONFIGURED:-1\r\n", prefix);
+ return;
+ }
+ for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
+ {
+ int j;
+
+ k++;
+ for (j = 0; j < Queue[i]->qg_numqueues; j++)
+ {
+ int n;
+
+ if (StopRequest)
+ stop_sendmail();
+
+ n = QSHM_ENTRIES(Queue[i]->qg_qpaths[j].qp_idx);
+ if (prefix != NULL)
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%s%s:%d\r\n",
+ prefix, qid_printqueue(i, j), n);
+ else if (n < 0)
+ {
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%s: unknown number of entries\n",
+ qid_printqueue(i, j));
+ unknown = true;
+ }
+ else if (n == 0)
+ {
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%s is empty\n",
+ qid_printqueue(i, j));
+ }
+ else if (n > 0)
+ {
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%s: entries=%d\n",
+ qid_printqueue(i, j), n);
+ nrequests += n;
+ k++;
+ }
+ }
+ }
+ if (prefix == NULL && k > 1)
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "\t\tTotal requests: %d%s\n",
+ nrequests, unknown ? " (about)" : "");
+#else /* SM_CONF_SHM */
+ if (prefix == NULL)
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "Data unavailable without shared memory support\n");
+ else
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%sNOTAVAILABLE:-1\r\n", prefix);
+#endif /* SM_CONF_SHM */
+}
+/*
+** PRINTQUEUE -- print out a representation of the mail queue
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Prints a listing of the mail queue on the standard output.
+*/
+
+void
+printqueue()
+{
+ int i, k = 0, nrequests = 0;
+
+ for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
+ {
+ int j;
+
+ k++;
+ for (j = 0; j < Queue[i]->qg_numqueues; j++)
+ {
+ if (StopRequest)
+ stop_sendmail();
+ nrequests += print_single_queue(i, j);
+ k++;
+ }
+ }
+ if (k > 1)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "\t\tTotal requests: %d\n",
+ nrequests);
+}
+/*
+** PRINT_SINGLE_QUEUE -- print out a representation of a single mail queue
+**
+** Parameters:
+** qgrp -- the index of the queue group.
+** qdir -- the queue directory.
+**
+** Returns:
+** number of requests in mail queue.
+**
+** Side Effects:
+** Prints a listing of the mail queue on the standard output.
+*/
+
+int
+print_single_queue(qgrp, qdir)
+ int qgrp;
+ int qdir;
+{
+ register WORK *w;
+ SM_FILE_T *f;
+ int nrequests;
+ char qd[MAXPATHLEN];
+ char qddf[MAXPATHLEN];
+ char buf[MAXLINE];
+
+ if (qdir == NOQDIR)
+ {
+ (void) sm_strlcpy(qd, ".", sizeof(qd));
+ (void) sm_strlcpy(qddf, ".", sizeof(qddf));
+ }
+ else
+ {
+ (void) sm_strlcpyn(qd, sizeof(qd), 2,
+ Queue[qgrp]->qg_qpaths[qdir].qp_name,
+ (bitset(QP_SUBQF,
+ Queue[qgrp]->qg_qpaths[qdir].qp_subdirs)
+ ? "/qf" : ""));
+ (void) sm_strlcpyn(qddf, sizeof(qddf), 2,
+ Queue[qgrp]->qg_qpaths[qdir].qp_name,
+ (bitset(QP_SUBDF,
+ Queue[qgrp]->qg_qpaths[qdir].qp_subdirs)
+ ? "/df" : ""));
+ }
+
+ /*
+ ** Check for permission to print the queue
+ */
+
+ if (bitset(PRIV_RESTRICTMAILQ, PrivacyFlags) && RealUid != 0)
+ {
+ struct stat st;
+#ifdef NGROUPS_MAX
+ int n;
+ extern GIDSET_T InitialGidSet[NGROUPS_MAX];
+#endif /* NGROUPS_MAX */
+
+ if (stat(qd, &st) < 0)
+ {
+ syserr("Cannot stat %s",
+ qid_printqueue(qgrp, qdir));
+ return 0;
+ }
+#ifdef NGROUPS_MAX
+ n = NGROUPS_MAX;
+ while (--n >= 0)
+ {
+ if (InitialGidSet[n] == st.st_gid)
+ break;
+ }
+ if (n < 0 && RealGid != st.st_gid)
+#else /* NGROUPS_MAX */
+ if (RealGid != st.st_gid)
+#endif /* NGROUPS_MAX */
+ {
+ usrerr("510 You are not permitted to see the queue");
+ setstat(EX_NOPERM);
+ return 0;
+ }
+ }
+
+ /*
+ ** Read and order the queue.
+ */
+
+ nrequests = gatherq(qgrp, qdir, true, NULL, NULL);
+ (void) sortq(Queue[qgrp]->qg_maxlist);
+
+ /*
+ ** Print the work list that we have read.
+ */
+
+ /* first see if there is anything */
+ if (nrequests <= 0)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%s is empty\n",
+ qid_printqueue(qgrp, qdir));
+ return 0;
+ }
+
+ sm_getla(); /* get load average */
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "\t\t%s (%d request%s",
+ qid_printqueue(qgrp, qdir),
+ nrequests, nrequests == 1 ? "" : "s");
+ if (MaxQueueRun > 0 && nrequests > MaxQueueRun)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ ", only %d printed", MaxQueueRun);
+ if (Verbose)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ ")\n-----Q-ID----- --Size-- -Priority- ---Q-Time--- --------Sender/Recipient--------\n");
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ ")\n-----Q-ID----- --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;
+ int flags = 0;
+ int qfver;
+ char quarmsg[MAXLINE];
+ char statmsg[MAXLINE];
+ char bodytype[MAXNAME + 1];
+ char qf[MAXPATHLEN];
+
+ if (StopRequest)
+ stop_sendmail();
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "%13s",
+ w->w_name + 2);
+ (void) sm_strlcpyn(qf, sizeof(qf), 3, qd, "/", w->w_name);
+ f = sm_io_open(SmFtStdio, SM_TIME_DEFAULT, qf, SM_IO_RDONLY_B,
+ NULL);
+ if (f == NULL)
+ {
+ if (errno == EPERM)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " (permission denied)\n");
+ else if (errno == ENOENT)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " (job completed)\n");
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " (%s)\n",
+ sm_errstring(errno));
+ errno = 0;
+ continue;
+ }
+ w->w_name[0] = DATAFL_LETTER;
+ (void) sm_strlcpyn(qf, sizeof(qf), 3, qddf, "/", w->w_name);
+ if (stat(qf, &st) >= 0)
+ dfsize = st.st_size;
+ else
+ {
+ ENVELOPE e;
+
+ /*
+ ** Maybe the df file can't be statted because
+ ** it is in a different directory than the qf file.
+ ** In order to find out, we must read the qf file.
+ */
+
+ newenvelope(&e, &BlankEnvelope, sm_rpool_new_x(NULL));
+ e.e_id = w->w_name + 2;
+ e.e_qgrp = qgrp;
+ e.e_qdir = qdir;
+ dfsize = -1;
+ if (readqf(&e, false))
+ {
+ char *df = queuename(&e, DATAFL_LETTER);
+ if (stat(df, &st) >= 0)
+ dfsize = st.st_size;
+ }
+ if (e.e_lockfp != NULL)
+ {
+ (void) sm_io_close(e.e_lockfp, SM_TIME_DEFAULT);
+ e.e_lockfp = NULL;
+ }
+ clearenvelope(&e, false, e.e_rpool);
+ sm_rpool_free(e.e_rpool);
+ }
+ if (w->w_lock)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "*");
+ else if (QueueMode == QM_LOST)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "?");
+ else if (w->w_tooyoung)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "-");
+ else if (shouldqueue(w->w_pri, w->w_ctime))
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "X");
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, " ");
+
+ errno = 0;
+
+ quarmsg[0] = '\0';
+ statmsg[0] = bodytype[0] = '\0';
+ qfver = 0;
+ while (sm_io_fgets(f, SM_TIME_DEFAULT, buf, sizeof(buf)) != NULL)
+ {
+ register int i;
+ register char *p;
+
+ if (StopRequest)
+ stop_sendmail();
+
+ fixcrlf(buf, true);
+ switch (buf[0])
+ {
+ case 'V': /* queue file version */
+ qfver = atoi(&buf[1]);
+ break;
+
+ case 'M': /* error message */
+ if ((i = strlen(&buf[1])) >= sizeof(statmsg))
+ i = sizeof(statmsg) - 1;
+ memmove(statmsg, &buf[1], i);
+ statmsg[i] = '\0';
+ break;
+
+ case 'q': /* quarantine reason */
+ if ((i = strlen(&buf[1])) >= sizeof(quarmsg))
+ i = sizeof(quarmsg) - 1;
+ memmove(quarmsg, &buf[1], i);
+ quarmsg[i] = '\0';
+ break;
+
+ case 'B': /* body type */
+ if ((i = strlen(&buf[1])) >= sizeof(bodytype))
+ i = sizeof(bodytype) - 1;
+ memmove(bodytype, &buf[1], i);
+ bodytype[i] = '\0';
+ break;
+
+ case 'S': /* sender name */
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%8ld %10ld%c%.12s ",
+ dfsize,
+ w->w_pri,
+ bitset(EF_WARNING, flags)
+ ? '+' : ' ',
+ ctime(&submittime) + 4);
+ prtstr(&buf[1], 78);
+ }
+ else
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%8ld %.16s ",
+ dfsize,
+ ctime(&submittime));
+ prtstr(&buf[1], 39);
+ }
+
+ if (quarmsg[0] != '\0')
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n QUARANTINE: %.*s",
+ Verbose ? 100 : 60,
+ quarmsg);
+ quarmsg[0] = '\0';
+ }
+
+ if (statmsg[0] != '\0' || bodytype[0] != '\0')
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n %10.10s",
+ bodytype);
+ if (statmsg[0] != '\0')
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ " (%.*s)",
+ Verbose ? 100 : 60,
+ statmsg);
+ statmsg[0] = '\0';
+ }
+ break;
+
+ case 'C': /* controlling user */
+ if (Verbose)
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n\t\t\t\t\t\t(---%.64s---)",
+ &buf[1]);
+ break;
+
+ case 'R': /* recipient name */
+ p = &buf[1];
+ if (qfver >= 1)
+ {
+ p = strchr(p, ':');
+ if (p == NULL)
+ break;
+ p++;
+ }
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n\t\t\t\t\t\t");
+ prtstr(p, 71);
+ }
+ else
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n\t\t\t\t\t ");
+ prtstr(p, 38);
+ }
+ if (Verbose && statmsg[0] != '\0')
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "\n\t\t (%.100s)",
+ statmsg);
+ statmsg[0] = '\0';
+ }
+ break;
+
+ case 'T': /* creation time */
+ submittime = atol(&buf[1]);
+ break;
+
+ case 'F': /* flag bits */
+ for (p = &buf[1]; *p != '\0'; p++)
+ {
+ switch (*p)
+ {
+ case 'w':
+ flags |= EF_WARNING;
+ break;
+ }
+ }
+ }
+ }
+ if (submittime == (time_t) 0)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ " (no control file)");
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT, "\n");
+ (void) sm_io_close(f, SM_TIME_DEFAULT);
+ }
+ return nrequests;
+}
+
+/*
+** QUEUE_LETTER -- get the proper queue letter for the current QueueMode.
+**
+** Parameters:
+** e -- envelope to build it in/from.
+** type -- the file type, used as the first character
+** of the file name.
+**
+** Returns:
+** the letter to use
+*/
+
+static char
+queue_letter(e, type)
+ ENVELOPE *e;
+ int type;
+{
+ /* Change type according to QueueMode */
+ if (type == ANYQFL_LETTER)
+ {
+ if (e->e_quarmsg != NULL)
+ type = QUARQF_LETTER;
+ else
+ {
+ switch (QueueMode)
+ {
+ case QM_NORMAL:
+ type = NORMQF_LETTER;
+ break;
+
+ case QM_QUARANTINE:
+ type = QUARQF_LETTER;
+ break;
+
+ case QM_LOST:
+ type = LOSEQF_LETTER;
+ break;
+
+ default:
+ /* should never happen */
+ abort();
+ /* NOTREACHED */
+ }
+ }
+ }
+ return type;
+}
+
+/*
+** QUEUENAME -- build a file name in the queue directory for this envelope.
+**
+** 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 queue name (in a static buffer).
+**
+** Side Effects:
+** If no id code is already assigned, queuename() will
+** assign an id code with assign_queueid(). If no queue
+** directory is assigned, one will be set with setnewqueue().
+*/
+
+char *
+queuename(e, type)
+ register ENVELOPE *e;
+ int type;
+{
+ int qd, qg;
+ char *sub = "/";
+ char pref[3];
+ static char buf[MAXPATHLEN];
+
+ /* Assign an ID if needed */
+ if (e->e_id == NULL)
+ assign_queueid(e);
+ type = queue_letter(e, type);
+
+ /* begin of filename */
+ pref[0] = (char) type;
+ pref[1] = 'f';
+ pref[2] = '\0';
+
+ /* Assign a queue group/directory if needed */
+ if (type == XSCRPT_LETTER)
+ {
+ /*
+ ** We don't want to call setnewqueue() if we are fetching
+ ** the pathname of the transcript file, because setnewqueue
+ ** chooses a queue, and sometimes we need to write to the
+ ** transcript file before we have gathered enough information
+ ** to choose a queue.
+ */
+
+ if (e->e_xfqgrp == NOQGRP || e->e_xfqdir == NOQDIR)
+ {
+ if (e->e_qgrp != NOQGRP && e->e_qdir != NOQDIR)
+ {
+ e->e_xfqgrp = e->e_qgrp;
+ e->e_xfqdir = e->e_qdir;
+ }
+ else
+ {
+ e->e_xfqgrp = 0;
+ if (Queue[e->e_xfqgrp]->qg_numqueues <= 1)
+ e->e_xfqdir = 0;
+ else
+ {
+ e->e_xfqdir = get_rand_mod(
+ Queue[e->e_xfqgrp]->qg_numqueues);
+ }
+ }
+ }
+ qd = e->e_xfqdir;
+ qg = e->e_xfqgrp;
+ }
+ else
+ {
+ if (e->e_qgrp == NOQGRP || e->e_qdir == NOQDIR)
+ (void) setnewqueue(e);
+ if (type == DATAFL_LETTER)
+ {
+ qd = e->e_dfqdir;
+ qg = e->e_dfqgrp;
+ }
+ else
+ {
+ qd = e->e_qdir;
+ qg = e->e_qgrp;
+ }
+ }
+
+ /* xf files always have a valid qd and qg picked above */
+ if ((qd == NOQDIR || qg == NOQGRP) && type != XSCRPT_LETTER)
+ (void) sm_strlcpyn(buf, sizeof(buf), 2, pref, e->e_id);
+ else
+ {
+ switch (type)
+ {
+ case DATAFL_LETTER:
+ if (bitset(QP_SUBDF, Queue[qg]->qg_qpaths[qd].qp_subdirs))
+ sub = "/df/";
+ break;
+
+ case QUARQF_LETTER:
+ case TEMPQF_LETTER:
+ case NEWQFL_LETTER:
+ case LOSEQF_LETTER:
+ case NORMQF_LETTER:
+ if (bitset(QP_SUBQF, Queue[qg]->qg_qpaths[qd].qp_subdirs))
+ sub = "/qf/";
+ break;
+
+ case XSCRPT_LETTER:
+ if (bitset(QP_SUBXF, Queue[qg]->qg_qpaths[qd].qp_subdirs))
+ sub = "/xf/";
+ break;
+
+ default:
+ sm_abort("queuename: bad queue file type %d", type);
+ }
+
+ (void) sm_strlcpyn(buf, sizeof(buf), 4,
+ Queue[qg]->qg_qpaths[qd].qp_name,
+ sub, pref, e->e_id);
+ }
+
+ if (tTd(7, 2))
+ sm_dprintf("queuename: %s\n", buf);
+ return buf;
+}
+
+/*
+** INIT_QID_ALG -- Initialize the (static) parameters that are used to
+** generate a queue ID.
+**
+** This function is called by the daemon to reset
+** LastQueueTime and LastQueuePid which are used by assign_queueid().
+** Otherwise the algorithm may cause problems because
+** LastQueueTime and LastQueuePid are set indirectly by main()
+** before the daemon process is started, hence LastQueuePid is not
+** the pid of the daemon and therefore a child of the daemon can
+** actually have the same pid as LastQueuePid which means the section
+** in assign_queueid():
+** * see if we need to get a new base time/pid *
+** is NOT triggered which will cause the same queue id to be generated.
+**
+** Parameters:
+** none
+**
+** Returns:
+** none.
+*/
+
+void
+init_qid_alg()
+{
+ LastQueueTime = 0;
+ LastQueuePid = -1;
+}
+
+/*
+** ASSIGN_QUEUEID -- assign a queue ID for this envelope.
+**
+** Assigns an id code if one does not already exist.
+** This code assumes that nothing will remain in the queue for
+** longer than 60 years. It is critical that files with the given
+** name do not already exist in the queue.
+** [No longer initializes e_qdir to NOQDIR.]
+**
+** Parameters:
+** e -- envelope to set it in.
+**
+** Returns:
+** none.
+*/
+
+static const char QueueIdChars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+# define QIC_LEN 60
+# define QIC_LEN_R 62
+
+/*
+** Note: the length is "officially" 60 because minutes and seconds are
+** usually only 0-59. However (Linux):
+** tm_sec The number of seconds after the minute, normally in
+** the range 0 to 59, but can be up to 61 to allow for
+** leap seconds.
+** Hence the real length of the string is 62 to take this into account.
+** Alternatively % QIC_LEN can (should) be used for access everywhere.
+*/
+
+# define queuenextid() CurrentPid
+
+
+void
+assign_queueid(e)
+ register ENVELOPE *e;
+{
+ pid_t pid = queuenextid();
+ static int cX = 0;
+ static long random_offset;
+ struct tm *tm;
+ char idbuf[MAXQFNAME - 2];
+ int seq;
+
+ if (e->e_id != NULL)
+ return;
+
+ /* see if we need to get a new base time/pid */
+ if (cX >= QIC_LEN * QIC_LEN || LastQueueTime == 0 ||
+ LastQueuePid != pid)
+ {
+ time_t then = LastQueueTime;
+
+ /* if the first time through, pick a random offset */
+ if (LastQueueTime == 0)
+ random_offset = get_random();
+
+ while ((LastQueueTime = curtime()) == then &&
+ LastQueuePid == pid)
+ {
+ (void) sleep(1);
+ }
+ LastQueuePid = queuenextid();
+ cX = 0;
+ }
+
+ /*
+ ** Generate a new sequence number between 0 and QIC_LEN*QIC_LEN-1.
+ ** This lets us generate up to QIC_LEN*QIC_LEN unique queue ids
+ ** per second, per process. With envelope splitting,
+ ** a single message can consume many queue ids.
+ */
+
+ seq = (int)((cX + random_offset) % (QIC_LEN * QIC_LEN));
+ ++cX;
+ if (tTd(7, 50))
+ sm_dprintf("assign_queueid: random_offset = %ld (%d)\n",
+ random_offset, seq);
+
+ tm = gmtime(&LastQueueTime);
+ idbuf[0] = QueueIdChars[tm->tm_year % QIC_LEN];
+ idbuf[1] = QueueIdChars[tm->tm_mon];
+ idbuf[2] = QueueIdChars[tm->tm_mday];
+ idbuf[3] = QueueIdChars[tm->tm_hour];
+ idbuf[4] = QueueIdChars[tm->tm_min % QIC_LEN_R];
+ idbuf[5] = QueueIdChars[tm->tm_sec % QIC_LEN_R];
+ idbuf[6] = QueueIdChars[seq / QIC_LEN];
+ idbuf[7] = QueueIdChars[seq % QIC_LEN];
+ (void) sm_snprintf(&idbuf[8], sizeof(idbuf) - 8, "%06d",
+ (int) LastQueuePid);
+ e->e_id = sm_rpool_strdup_x(e->e_rpool, idbuf);
+ macdefine(&e->e_macro, A_PERM, 'i', e->e_id);
+#if 0
+ /* XXX: inherited from MainEnvelope */
+ e->e_qgrp = NOQGRP; /* too early to do anything else */
+ e->e_qdir = NOQDIR;
+ e->e_xfqgrp = NOQGRP;
+#endif /* 0 */
+
+ /* New ID means it's not on disk yet */
+ e->e_qfletter = '\0';
+
+ if (tTd(7, 1))
+ sm_dprintf("assign_queueid: assigned id %s, e=%p\n",
+ e->e_id, e);
+ if (LogLevel > 93)
+ sm_syslog(LOG_DEBUG, e->e_id, "assigned id");
+}
+/*
+** SYNC_QUEUE_TIME -- Assure exclusive PID in any given second
+**
+** Make sure one PID can't be used by two processes in any one second.
+**
+** If the system rotates PIDs fast enough, may get the
+** same pid in the same second for two distinct processes.
+** This will interfere with the queue file naming system.
+**
+** Parameters:
+** none
+**
+** Returns:
+** none
+*/
+
+void
+sync_queue_time()
+{
+#if FAST_PID_RECYCLE
+ if (OpMode != MD_TEST &&
+ OpMode != MD_VERIFY &&
+ LastQueueTime > 0 &&
+ LastQueuePid == CurrentPid &&
+ curtime() == LastQueueTime)
+ (void) sleep(1);
+#endif /* FAST_PID_RECYCLE */
+}
+/*
+** UNLOCKQUEUE -- unlock the queue entry for a specified envelope
+**
+** Parameters:
+** e -- the envelope to unlock.
+**
+** Returns:
+** none
+**
+** Side Effects:
+** unlocks the queue for `e'.
+*/
+
+void
+unlockqueue(e)
+ ENVELOPE *e;
+{
+ if (tTd(51, 4))
+ sm_dprintf("unlockqueue(%s)\n",
+ e->e_id == NULL ? "NOQUEUE" : e->e_id);
+
+
+ /* if there is a lock file in the envelope, close it */
+ if (e->e_lockfp != NULL)
+ (void) sm_io_close(e->e_lockfp, SM_TIME_DEFAULT);
+ e->e_lockfp = NULL;
+
+ /* don't create a queue id if we don't already have one */
+ if (e->e_id == NULL)
+ return;
+
+ /* remove the transcript */
+ if (LogLevel > 87)
+ sm_syslog(LOG_DEBUG, e->e_id, "unlock");
+ if (!tTd(51, 104))
+ (void) xunlink(queuename(e, XSCRPT_LETTER));
+}
+/*
+** SETCTLUSER -- create a controlling address
+**
+** Create a fake "address" given only a local login name; this is
+** used as a "controlling user" for future recipient addresses.
+**
+** Parameters:
+** user -- the user name of the controlling user.
+** qfver -- the version stamp of this queue file.
+** e -- envelope
+**
+** Returns:
+** An address descriptor for the controlling user,
+** using storage allocated from e->e_rpool.
+**
+*/
+
+static ADDRESS *
+setctluser(user, qfver, e)
+ char *user;
+ int qfver;
+ ENVELOPE *e;
+{
+ register ADDRESS *a;
+ struct passwd *pw;
+ char *p;
+
+ /*
+ ** See if this clears our concept of controlling user.
+ */
+
+ if (user == NULL || *user == '\0')
+ return NULL;
+
+ /*
+ ** Set up addr fields for controlling user.
+ */
+
+ a = (ADDRESS *) sm_rpool_malloc_x(e->e_rpool, sizeof(*a));
+ memset((char *) a, '\0', sizeof(*a));
+
+ if (*user == ':')
+ {
+ p = &user[1];
+ a->q_user = sm_rpool_strdup_x(e->e_rpool, p);
+ }
+ else
+ {
+ p = strtok(user, ":");
+ a->q_user = sm_rpool_strdup_x(e->e_rpool, user);
+ if (qfver >= 2)
+ {
+ if ((p = strtok(NULL, ":")) != NULL)
+ a->q_uid = atoi(p);
+ if ((p = strtok(NULL, ":")) != NULL)
+ a->q_gid = atoi(p);
+ if ((p = strtok(NULL, ":")) != NULL)
+ {
+ char *o;
+
+ a->q_flags |= QGOODUID;
+
+ /* if there is another ':': restore it */
+ if ((o = strtok(NULL, ":")) != NULL && o > p)
+ o[-1] = ':';
+ }
+ }
+ else if ((pw = sm_getpwnam(user)) != NULL)
+ {
+ if (*pw->pw_dir == '\0')
+ a->q_home = NULL;
+ else if (strcmp(pw->pw_dir, "/") == 0)
+ a->q_home = "";
+ else
+ a->q_home = sm_rpool_strdup_x(e->e_rpool, pw->pw_dir);
+ a->q_uid = pw->pw_uid;
+ a->q_gid = pw->pw_gid;
+ a->q_flags |= QGOODUID;
+ }
+ }
+
+ a->q_flags |= QPRIMARY; /* flag as a "ctladdr" */
+ a->q_mailer = LocalMailer;
+ if (p == NULL)
+ a->q_paddr = sm_rpool_strdup_x(e->e_rpool, a->q_user);
+ else
+ a->q_paddr = sm_rpool_strdup_x(e->e_rpool, p);
+ return a;
+}
+/*
+** LOSEQFILE -- rename queue file with LOSEQF_LETTER & try to let someone know
+**
+** Parameters:
+** e -- the envelope (e->e_id will be used).
+** why -- reported to whomever can hear.
+**
+** Returns:
+** none.
+*/
+
+void
+loseqfile(e, why)
+ register ENVELOPE *e;
+ char *why;
+{
+ bool loseit = true;
+ char *p;
+ char buf[MAXPATHLEN];
+
+ if (e == NULL || e->e_id == NULL)
+ return;
+ p = queuename(e, ANYQFL_LETTER);
+ if (sm_strlcpy(buf, p, sizeof(buf)) >= sizeof(buf))
+ return;
+ if (!bitset(EF_INQUEUE, e->e_flags))
+ queueup(e, false, true);
+ else if (QueueMode == QM_LOST)
+ loseit = false;
+
+ /* if already lost, no need to re-lose */
+ if (loseit)
+ {
+ p = queuename(e, LOSEQF_LETTER);
+ if (rename(buf, p) < 0)
+ syserr("cannot rename(%s, %s), uid=%d",
+ buf, p, (int) geteuid());
+ else if (LogLevel > 0)
+ sm_syslog(LOG_ALERT, e->e_id,
+ "Losing %s: %s", buf, why);
+ }
+ if (e->e_dfp != NULL)
+ {
+ (void) sm_io_close(e->e_dfp, SM_TIME_DEFAULT);
+ e->e_dfp = NULL;
+ }
+ e->e_flags &= ~EF_HAS_DF;
+}
+/*
+** NAME2QID -- translate a queue group name to a queue group id
+**
+** Parameters:
+** queuename -- name of queue group.
+**
+** Returns:
+** queue group id if found.
+** NOQGRP otherwise.
+*/
+
+int
+name2qid(queuename)
+ char *queuename;
+{
+ register STAB *s;
+
+ s = stab(queuename, ST_QUEUE, ST_FIND);
+ if (s == NULL)
+ return NOQGRP;
+ return s->s_quegrp->qg_index;
+}
+/*
+** QID_PRINTNAME -- create externally printable version of queue id
+**
+** Parameters:
+** e -- the envelope.
+**
+** Returns:
+** a printable version
+*/
+
+char *
+qid_printname(e)
+ ENVELOPE *e;
+{
+ char *id;
+ static char idbuf[MAXQFNAME + 34];
+
+ if (e == NULL)
+ return "";
+
+ if (e->e_id == NULL)
+ id = "";
+ else
+ id = e->e_id;
+
+ if (e->e_qdir == NOQDIR)
+ return id;
+
+ (void) sm_snprintf(idbuf, sizeof(idbuf), "%.32s/%s",
+ Queue[e->e_qgrp]->qg_qpaths[e->e_qdir].qp_name,
+ id);
+ return idbuf;
+}
+/*
+** QID_PRINTQUEUE -- create full version of queue directory for data files
+**
+** Parameters:
+** qgrp -- index in queue group.
+** qdir -- the short version of the queue directory
+**
+** Returns:
+** the full pathname to the queue (might point to a static var)
+*/
+
+char *
+qid_printqueue(qgrp, qdir)
+ int qgrp;
+ int qdir;
+{
+ char *subdir;
+ static char dir[MAXPATHLEN];
+
+ if (qdir == NOQDIR)
+ return Queue[qgrp]->qg_qdir;
+
+ if (strcmp(Queue[qgrp]->qg_qpaths[qdir].qp_name, ".") == 0)
+ subdir = NULL;
+ else
+ subdir = Queue[qgrp]->qg_qpaths[qdir].qp_name;
+
+ (void) sm_strlcpyn(dir, sizeof(dir), 4,
+ Queue[qgrp]->qg_qdir,
+ subdir == NULL ? "" : "/",
+ subdir == NULL ? "" : subdir,
+ (bitset(QP_SUBDF,
+ Queue[qgrp]->qg_qpaths[qdir].qp_subdirs)
+ ? "/df" : ""));
+ return dir;
+}
+
+/*
+** PICKQDIR -- Pick a queue directory from a queue group
+**
+** Parameters:
+** qg -- queue group
+** fsize -- file size in bytes
+** e -- envelope, or NULL
+**
+** Result:
+** NOQDIR if no queue directory in qg has enough free space to
+** hold a file of size 'fsize', otherwise the index of
+** a randomly selected queue directory which resides on a
+** file system with enough disk space.
+** XXX This could be extended to select a queuedir with
+** a few (the fewest?) number of entries. That data
+** is available if shared memory is used.
+**
+** Side Effects:
+** If the request fails and e != NULL then sm_syslog is called.
+*/
+
+int
+pickqdir(qg, fsize, e)
+ QUEUEGRP *qg;
+ long fsize;
+ ENVELOPE *e;
+{
+ int qdir;
+ int i;
+ long avail = 0;
+
+ /* Pick a random directory, as a starting point. */
+ if (qg->qg_numqueues <= 1)
+ qdir = 0;
+ else
+ qdir = get_rand_mod(qg->qg_numqueues);
+
+ if (MinBlocksFree <= 0 && fsize <= 0)
+ return qdir;
+
+ /*
+ ** Now iterate over the queue directories,
+ ** looking for a directory with enough space for this message.
+ */
+
+ i = qdir;
+ do
+ {
+ QPATHS *qp = &qg->qg_qpaths[i];
+ long needed = 0;
+ long fsavail = 0;
+
+ if (fsize > 0)
+ needed += fsize / FILE_SYS_BLKSIZE(qp->qp_fsysidx)
+ + ((fsize % FILE_SYS_BLKSIZE(qp->qp_fsysidx)
+ > 0) ? 1 : 0);
+ if (MinBlocksFree > 0)
+ needed += MinBlocksFree;
+ fsavail = FILE_SYS_AVAIL(qp->qp_fsysidx);
+#if SM_CONF_SHM
+ if (fsavail <= 0)
+ {
+ long blksize;
+
+ /*
+ ** might be not correctly updated,
+ ** let's try to get the info directly.
+ */
+
+ fsavail = freediskspace(FILE_SYS_NAME(qp->qp_fsysidx),
+ &blksize);
+ if (fsavail < 0)
+ fsavail = 0;
+ }
+#endif /* SM_CONF_SHM */
+ if (needed <= fsavail)
+ return i;
+ if (avail < fsavail)
+ avail = fsavail;
+
+ if (qg->qg_numqueues > 0)
+ i = (i + 1) % qg->qg_numqueues;
+ } while (i != qdir);
+
+ if (e != NULL && LogLevel > 0)
+ sm_syslog(LOG_ALERT, e->e_id,
+ "low on space (%s needs %ld bytes + %ld blocks in %s), max avail: %ld",
+ CurHostName == NULL ? "SMTP-DAEMON" : CurHostName,
+ fsize, MinBlocksFree,
+ qg->qg_qdir, avail);
+ return NOQDIR;
+}
+/*
+** SETNEWQUEUE -- Sets a new queue group and directory
+**
+** Assign a queue group and directory to an envelope and store the
+** directory in e->e_qdir.
+**
+** Parameters:
+** e -- envelope to assign a queue for.
+**
+** Returns:
+** true if successful
+** false otherwise
+**
+** Side Effects:
+** On success, e->e_qgrp and e->e_qdir are non-negative.
+** On failure (not enough disk space),
+** e->qgrp = NOQGRP, e->e_qdir = NOQDIR
+** and usrerr() is invoked (which could raise an exception).
+*/
+
+bool
+setnewqueue(e)
+ ENVELOPE *e;
+{
+ if (tTd(41, 20))
+ sm_dprintf("setnewqueue: called\n");
+
+ /* not set somewhere else */
+ if (e->e_qgrp == NOQGRP)
+ {
+ ADDRESS *q;
+
+ /*
+ ** Use the queue group of the "first" recipient, as set by
+ ** the "queuegroup" rule set. If that is not defined, then
+ ** use the queue group of the mailer of the first recipient.
+ ** If that is not defined either, then use the default
+ ** queue group.
+ ** Notice: "first" depends on the sorting of sendqueue
+ ** in recipient().
+ ** To avoid problems with "bad" recipients look
+ ** for a valid address first.
+ */
+
+ q = e->e_sendqueue;
+ while (q != NULL &&
+ (QS_IS_BADADDR(q->q_state) || QS_IS_DEAD(q->q_state)))
+ {
+ q = q->q_next;
+ }
+ if (q == NULL)
+ e->e_qgrp = 0;
+ else if (q->q_qgrp >= 0)
+ e->e_qgrp = q->q_qgrp;
+ else if (q->q_mailer != NULL &&
+ ISVALIDQGRP(q->q_mailer->m_qgrp))
+ e->e_qgrp = q->q_mailer->m_qgrp;
+ else
+ e->e_qgrp = 0;
+ e->e_dfqgrp = e->e_qgrp;
+ }
+
+ if (ISVALIDQDIR(e->e_qdir) && ISVALIDQDIR(e->e_dfqdir))
+ {
+ if (tTd(41, 20))
+ sm_dprintf("setnewqueue: e_qdir already assigned (%s)\n",
+ qid_printqueue(e->e_qgrp, e->e_qdir));
+ return true;
+ }
+
+ filesys_update();
+ e->e_qdir = pickqdir(Queue[e->e_qgrp], e->e_msgsize, e);
+ if (e->e_qdir == NOQDIR)
+ {
+ e->e_qgrp = NOQGRP;
+ if (!bitset(EF_FATALERRS, e->e_flags))
+ usrerr("452 4.4.5 Insufficient disk space; try again later");
+ e->e_flags |= EF_FATALERRS;
+ return false;
+ }
+
+ if (tTd(41, 3))
+ sm_dprintf("setnewqueue: Assigned queue directory %s\n",
+ qid_printqueue(e->e_qgrp, e->e_qdir));
+
+ if (e->e_xfqgrp == NOQGRP || e->e_xfqdir == NOQDIR)
+ {
+ e->e_xfqgrp = e->e_qgrp;
+ e->e_xfqdir = e->e_qdir;
+ }
+ e->e_dfqdir = e->e_qdir;
+ return true;
+}
+/*
+** CHKQDIR -- check a queue directory
+**
+** Parameters:
+** name -- name of queue directory
+** sff -- flags for safefile()
+**
+** Returns:
+** is it a queue directory?
+*/
+
+static bool chkqdir __P((char *, long));
+
+static bool
+chkqdir(name, sff)
+ char *name;
+ long sff;
+{
+ struct stat statb;
+ int i;
+
+ /* skip over . and .. directories */
+ if (name[0] == '.' &&
+ (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')))
+ return false;
+#if HASLSTAT
+ if (lstat(name, &statb) < 0)
+#else /* HASLSTAT */
+ if (stat(name, &statb) < 0)
+#endif /* HASLSTAT */
+ {
+ if (tTd(41, 2))
+ sm_dprintf("chkqdir: stat(\"%s\"): %s\n",
+ name, sm_errstring(errno));
+ return false;
+ }
+#if HASLSTAT
+ if (S_ISLNK(statb.st_mode))
+ {
+ /*
+ ** For a symlink we need to make sure the
+ ** target is a directory
+ */
+
+ if (stat(name, &statb) < 0)
+ {
+ if (tTd(41, 2))
+ sm_dprintf("chkqdir: stat(\"%s\"): %s\n",
+ name, sm_errstring(errno));
+ return false;
+ }
+ }
+#endif /* HASLSTAT */
+
+ if (!S_ISDIR(statb.st_mode))
+ {
+ if (tTd(41, 2))
+ sm_dprintf("chkqdir: \"%s\": Not a directory\n",
+ name);
+ return false;
+ }
+
+ /* Print a warning if unsafe (but still use it) */
+ /* XXX do this only if we want the warning? */
+ i = safedirpath(name, RunAsUid, RunAsGid, NULL, sff, 0, 0);
+ if (i != 0)
+ {
+ if (tTd(41, 2))
+ sm_dprintf("chkqdir: \"%s\": Not safe: %s\n",
+ name, sm_errstring(i));
+#if _FFR_CHK_QUEUE
+ if (LogLevel > 8)
+ sm_syslog(LOG_WARNING, NOQID,
+ "queue directory \"%s\": Not safe: %s",
+ name, sm_errstring(i));
+#endif /* _FFR_CHK_QUEUE */
+ }
+ return true;
+}
+/*
+** MULTIQUEUE_CACHE -- cache a list of paths to queues.
+**
+** Each potential queue is checked as the cache is built.
+** Thereafter, each is blindly trusted.
+** Note that we can be called again after a timeout to rebuild
+** (although code for that is not ready yet).
+**
+** Parameters:
+** basedir -- base of all queue directories.
+** blen -- strlen(basedir).
+** qg -- queue group.
+** qn -- number of queue directories already cached.
+** phash -- pointer to hash value over queue dirs.
+#if SM_CONF_SHM
+** only used if shared memory is active.
+#endif * SM_CONF_SHM *
+**
+** Returns:
+** new number of queue directories.
+*/
+
+#define INITIAL_SLOTS 20
+#define ADD_SLOTS 10
+
+static int
+multiqueue_cache(basedir, blen, qg, qn, phash)
+ char *basedir;
+ int blen;
+ QUEUEGRP *qg;
+ int qn;
+ unsigned int *phash;
+{
+ char *cp;
+ int i, len;
+ int slotsleft = 0;
+ long sff = SFF_ANYFILE;
+ char qpath[MAXPATHLEN];
+ char subdir[MAXPATHLEN];
+ char prefix[MAXPATHLEN]; /* dir relative to basedir */
+
+ if (tTd(41, 20))
+ sm_dprintf("multiqueue_cache: called\n");
+
+ /* Initialize to current directory */
+ prefix[0] = '.';
+ prefix[1] = '\0';
+ if (qg->qg_numqueues != 0 && qg->qg_qpaths != NULL)
+ {
+ for (i = 0; i < qg->qg_numqueues; i++)
+ {
+ if (qg->qg_qpaths[i].qp_name != NULL)
+ (void) sm_free(qg->qg_qpaths[i].qp_name); /* XXX */
+ }
+ (void) sm_free((char *) qg->qg_qpaths); /* XXX */
+ qg->qg_qpaths = NULL;
+ qg->qg_numqueues = 0;
+ }
+
+ /* If running as root, allow safedirpath() checks to use privs */
+ if (RunAsUid == 0)
+ sff |= SFF_ROOTOK;
+#if _FFR_CHK_QUEUE
+ sff |= SFF_SAFEDIRPATH|SFF_NOWWFILES;
+ if (!UseMSP)
+ sff |= SFF_NOGWFILES;
+#endif /* _FFR_CHK_QUEUE */
+
+ if (!SM_IS_DIR_START(qg->qg_qdir))
+ {
+ /*
+ ** XXX we could add basedir, but then we have to realloc()
+ ** the string... Maybe another time.
+ */
+
+ syserr("QueuePath %s not absolute", qg->qg_qdir);
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+
+ /* qpath: directory of current workgroup */
+ len = sm_strlcpy(qpath, qg->qg_qdir, sizeof(qpath));
+ if (len >= sizeof(qpath))
+ {
+ syserr("QueuePath %.256s too long (%d max)",
+ qg->qg_qdir, (int) sizeof(qpath));
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+
+ /* begin of qpath must be same as basedir */
+ if (strncmp(basedir, qpath, blen) != 0 &&
+ (strncmp(basedir, qpath, blen - 1) != 0 || len != blen - 1))
+ {
+ syserr("QueuePath %s not subpath of QueueDirectory %s",
+ qpath, basedir);
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+
+ /* Do we have a nested subdirectory? */
+ if (blen < len && SM_FIRST_DIR_DELIM(qg->qg_qdir + blen) != NULL)
+ {
+
+ /* Copy subdirectory into prefix for later use */
+ if (sm_strlcpy(prefix, qg->qg_qdir + blen, sizeof(prefix)) >=
+ sizeof(prefix))
+ {
+ syserr("QueuePath %.256s too long (%d max)",
+ qg->qg_qdir, (int) sizeof(qpath));
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+ cp = SM_LAST_DIR_DELIM(prefix);
+ SM_ASSERT(cp != NULL);
+ *cp = '\0'; /* cut off trailing / */
+ }
+
+ /* This is guaranteed by the basedir check above */
+ SM_ASSERT(len >= blen - 1);
+ cp = &qpath[len - 1];
+ if (*cp == '*')
+ {
+ register DIR *dp;
+ register struct dirent *d;
+ int off;
+ char *delim;
+ char relpath[MAXPATHLEN];
+
+ *cp = '\0'; /* Overwrite wildcard */
+ if ((cp = SM_LAST_DIR_DELIM(qpath)) == NULL)
+ {
+ syserr("QueueDirectory: can not wildcard relative path");
+ if (tTd(41, 2))
+ sm_dprintf("multiqueue_cache: \"%s*\": Can not wildcard relative path.\n",
+ qpath);
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+ if (cp == qpath)
+ {
+ /*
+ ** Special case of top level wildcard, like /foo*
+ ** Change to //foo*
+ */
+
+ (void) sm_strlcpy(qpath + 1, qpath, sizeof(qpath) - 1);
+ ++cp;
+ }
+ delim = cp;
+ *(cp++) = '\0'; /* Replace / with \0 */
+ len = strlen(cp); /* Last component of queue directory */
+
+ /*
+ ** Path relative to basedir, with trailing /
+ ** It will be modified below to specify the subdirectories
+ ** so they can be opened without chdir().
+ */
+
+ off = sm_strlcpyn(relpath, sizeof(relpath), 2, prefix, "/");
+ SM_ASSERT(off < sizeof(relpath));
+
+ if (tTd(41, 2))
+ sm_dprintf("multiqueue_cache: prefix=\"%s%s\"\n",
+ relpath, cp);
+
+ /* It is always basedir: we don't need to store it per group */
+ /* XXX: optimize this! -> one more global? */
+ qg->qg_qdir = newstr(basedir);
+ qg->qg_qdir[blen - 1] = '\0'; /* cut off trailing / */
+
+ /*
+ ** XXX Should probably wrap this whole loop in a timeout
+ ** in case some wag decides to NFS mount the queues.
+ */
+
+ /* Test path to get warning messages. */
+ if (qn == 0)
+ {
+ /* XXX qg_runasuid and qg_runasgid for specials? */
+ i = safedirpath(basedir, RunAsUid, RunAsGid, NULL,
+ sff, 0, 0);
+ if (i != 0 && tTd(41, 2))
+ sm_dprintf("multiqueue_cache: \"%s\": Not safe: %s\n",
+ basedir, sm_errstring(i));
+ }
+
+ if ((dp = opendir(prefix)) == NULL)
+ {
+ syserr("can not opendir(%s/%s)", qg->qg_qdir, prefix);
+ if (tTd(41, 2))
+ sm_dprintf("multiqueue_cache: opendir(\"%s/%s\"): %s\n",
+ qg->qg_qdir, prefix,
+ sm_errstring(errno));
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+ while ((d = readdir(dp)) != NULL)
+ {
+ /* Skip . and .. directories */
+ if (strcmp(d->d_name, ".") == 0 ||
+ strcmp(d->d_name, "..") == 0)
+ continue;
+
+ i = strlen(d->d_name);
+ if (i < len || strncmp(d->d_name, cp, len) != 0)
+ {
+ if (tTd(41, 5))
+ sm_dprintf("multiqueue_cache: \"%s\", skipped\n",
+ d->d_name);
+ continue;
+ }
+
+ /* Create relative pathname: prefix + local directory */
+ i = sizeof(relpath) - off;
+ if (sm_strlcpy(relpath + off, d->d_name, i) >= i)
+ continue; /* way too long */
+
+ if (!chkqdir(relpath, sff))
+ continue;
+
+ if (qg->qg_qpaths == NULL)
+ {
+ slotsleft = INITIAL_SLOTS;
+ qg->qg_qpaths = (QPATHS *)xalloc((sizeof(*qg->qg_qpaths)) *
+ slotsleft);
+ qg->qg_numqueues = 0;
+ }
+ else if (slotsleft < 1)
+ {
+ qg->qg_qpaths = (QPATHS *)sm_realloc((char *)qg->qg_qpaths,
+ (sizeof(*qg->qg_qpaths)) *
+ (qg->qg_numqueues +
+ ADD_SLOTS));
+ if (qg->qg_qpaths == NULL)
+ {
+ (void) closedir(dp);
+ return qn;
+ }
+ slotsleft += ADD_SLOTS;
+ }
+
+ /* check subdirs */
+ qg->qg_qpaths[qg->qg_numqueues].qp_subdirs = QP_NOSUB;
+
+#define CHKRSUBDIR(name, flag) \
+ (void) sm_strlcpyn(subdir, sizeof(subdir), 3, relpath, "/", name); \
+ if (chkqdir(subdir, sff)) \
+ qg->qg_qpaths[qg->qg_numqueues].qp_subdirs |= flag; \
+ else
+
+
+ CHKRSUBDIR("qf", QP_SUBQF);
+ CHKRSUBDIR("df", QP_SUBDF);
+ CHKRSUBDIR("xf", QP_SUBXF);
+
+ /* assert(strlen(d->d_name) < MAXPATHLEN - 14) */
+ /* maybe even - 17 (subdirs) */
+
+ if (prefix[0] != '.')
+ qg->qg_qpaths[qg->qg_numqueues].qp_name =
+ newstr(relpath);
+ else
+ qg->qg_qpaths[qg->qg_numqueues].qp_name =
+ newstr(d->d_name);
+
+ if (tTd(41, 2))
+ sm_dprintf("multiqueue_cache: %d: \"%s\" cached (%x).\n",
+ qg->qg_numqueues, relpath,
+ qg->qg_qpaths[qg->qg_numqueues].qp_subdirs);
+#if SM_CONF_SHM
+ qg->qg_qpaths[qg->qg_numqueues].qp_idx = qn;
+ *phash = hash_q(relpath, *phash);
+#endif /* SM_CONF_SHM */
+ qg->qg_numqueues++;
+ ++qn;
+ slotsleft--;
+ }
+ (void) closedir(dp);
+
+ /* undo damage */
+ *delim = '/';
+ }
+ if (qg->qg_numqueues == 0)
+ {
+ qg->qg_qpaths = (QPATHS *) xalloc(sizeof(*qg->qg_qpaths));
+
+ /* test path to get warning messages */
+ i = safedirpath(qpath, RunAsUid, RunAsGid, NULL, sff, 0, 0);
+ if (i == ENOENT)
+ {
+ syserr("can not opendir(%s)", qpath);
+ if (tTd(41, 2))
+ sm_dprintf("multiqueue_cache: opendir(\"%s\"): %s\n",
+ qpath, sm_errstring(i));
+ ExitStat = EX_CONFIG;
+ return qn;
+ }
+
+ qg->qg_qpaths[0].qp_subdirs = QP_NOSUB;
+ qg->qg_numqueues = 1;
+
+ /* check subdirs */
+#define CHKSUBDIR(name, flag) \
+ (void) sm_strlcpyn(subdir, sizeof(subdir), 3, qg->qg_qdir, "/", name); \
+ if (chkqdir(subdir, sff)) \
+ qg->qg_qpaths[0].qp_subdirs |= flag; \
+ else
+
+ CHKSUBDIR("qf", QP_SUBQF);
+ CHKSUBDIR("df", QP_SUBDF);
+ CHKSUBDIR("xf", QP_SUBXF);
+
+ if (qg->qg_qdir[blen - 1] != '\0' &&
+ qg->qg_qdir[blen] != '\0')
+ {
+ /*
+ ** Copy the last component into qpaths and
+ ** cut off qdir
+ */
+
+ qg->qg_qpaths[0].qp_name = newstr(qg->qg_qdir + blen);
+ qg->qg_qdir[blen - 1] = '\0';
+ }
+ else
+ qg->qg_qpaths[0].qp_name = newstr(".");
+
+#if SM_CONF_SHM
+ qg->qg_qpaths[0].qp_idx = qn;
+ *phash = hash_q(qg->qg_qpaths[0].qp_name, *phash);
+#endif /* SM_CONF_SHM */
+ ++qn;
+ }
+ return qn;
+}
+
+/*
+** FILESYS_FIND -- find entry in FileSys table, or add new one
+**
+** Given the pathname of a directory, determine the file system
+** in which that directory resides, and return a pointer to the
+** entry in the FileSys table that describes the file system.
+** A new entry is added if necessary (and requested).
+** If the directory does not exist, -1 is returned.
+**
+** Parameters:
+** name -- name of directory (must be persistent!)
+** path -- pathname of directory (name plus maybe "/df")
+** add -- add to structure if not found.
+**
+** Returns:
+** >=0: found: index in file system table
+** <0: some error, i.e.,
+** FSF_TOO_MANY: too many filesystems (-> syserr())
+** FSF_STAT_FAIL: can't stat() filesystem (-> syserr())
+** FSF_NOT_FOUND: not in list
+*/
+
+static short filesys_find __P((const char *, const char *, bool));
+
+#define FSF_NOT_FOUND (-1)
+#define FSF_STAT_FAIL (-2)
+#define FSF_TOO_MANY (-3)
+
+static short
+filesys_find(name, path, add)
+ const char *name;
+ const char *path;
+ bool add;
+{
+ struct stat st;
+ short i;
+
+ if (stat(path, &st) < 0)
+ {
+ syserr("cannot stat queue directory %s", path);
+ return FSF_STAT_FAIL;
+ }
+ for (i = 0; i < NumFileSys; ++i)
+ {
+ if (FILE_SYS_DEV(i) == st.st_dev)
+ {
+ /*
+ ** Make sure the file system (FS) name is set:
+ ** even though the source code indicates that
+ ** FILE_SYS_DEV() is only set below, it could be
+ ** set via shared memory, hence we need to perform
+ ** this check/assignment here.
+ */
+
+ if (NULL == FILE_SYS_NAME(i))
+ FILE_SYS_NAME(i) = name;
+ return i;
+ }
+ }
+ if (i >= MAXFILESYS)
+ {
+ syserr("too many queue file systems (%d max)", MAXFILESYS);
+ return FSF_TOO_MANY;
+ }
+ if (!add)
+ return FSF_NOT_FOUND;
+
+ ++NumFileSys;
+ FILE_SYS_NAME(i) = name;
+ FILE_SYS_DEV(i) = st.st_dev;
+ FILE_SYS_AVAIL(i) = 0;
+ FILE_SYS_BLKSIZE(i) = 1024; /* avoid divide by zero */
+ return i;
+}
+
+/*
+** FILESYS_SETUP -- set up mapping from queue directories to file systems
+**
+** This data structure is used to efficiently check the amount of
+** free space available in a set of queue directories.
+**
+** Parameters:
+** add -- initialize structure if necessary.
+**
+** Returns:
+** 0: success
+** <0: some error, i.e.,
+** FSF_NOT_FOUND: not in list
+** FSF_STAT_FAIL: can't stat() filesystem (-> syserr())
+** FSF_TOO_MANY: too many filesystems (-> syserr())
+*/
+
+static int filesys_setup __P((bool));
+
+static int
+filesys_setup(add)
+ bool add;
+{
+ int i, j;
+ short fs;
+ int ret;
+
+ ret = 0;
+ for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
+ {
+ for (j = 0; j < Queue[i]->qg_numqueues; ++j)
+ {
+ QPATHS *qp = &Queue[i]->qg_qpaths[j];
+ char qddf[MAXPATHLEN];
+
+ (void) sm_strlcpyn(qddf, sizeof(qddf), 2, qp->qp_name,
+ (bitset(QP_SUBDF, qp->qp_subdirs)
+ ? "/df" : ""));
+ fs = filesys_find(qp->qp_name, qddf, add);
+ if (fs >= 0)
+ qp->qp_fsysidx = fs;
+ else
+ qp->qp_fsysidx = 0;
+ if (fs < ret)
+ ret = fs;
+ }
+ }
+ return ret;
+}
+
+/*
+** FILESYS_UPDATE -- update amount of free space on all file systems
+**
+** The FileSys table is used to cache the amount of free space
+** available on all queue directory file systems.
+** This function updates the cached information if it has expired.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Updates FileSys table.
+*/
+
+void
+filesys_update()
+{
+ int i;
+ long avail, blksize;
+ time_t now;
+ static time_t nextupdate = 0;
+
+#if SM_CONF_SHM
+ /*
+ ** Only the daemon updates the shared memory, i.e.,
+ ** if shared memory is available but the pid is not the
+ ** one of the daemon, then don't do anything.
+ */
+
+ if (ShmId != SM_SHM_NO_ID && DaemonPid != CurrentPid)
+ return;
+#endif /* SM_CONF_SHM */
+ now = curtime();
+ if (now < nextupdate)
+ return;
+ nextupdate = now + FILESYS_UPDATE_INTERVAL;
+ for (i = 0; i < NumFileSys; ++i)
+ {
+ FILESYS *fs = &FILE_SYS(i);
+
+ avail = freediskspace(FILE_SYS_NAME(i), &blksize);
+ if (avail < 0 || blksize <= 0)
+ {
+ if (LogLevel > 5)
+ sm_syslog(LOG_ERR, NOQID,
+ "filesys_update failed: %s, fs=%s, avail=%ld, blocksize=%ld",
+ sm_errstring(errno),
+ FILE_SYS_NAME(i), avail, blksize);
+ fs->fs_avail = 0;
+ fs->fs_blksize = 1024; /* avoid divide by zero */
+ nextupdate = now + 2; /* let's do this soon again */
+ }
+ else
+ {
+ fs->fs_avail = avail;
+ fs->fs_blksize = blksize;
+ }
+ }
+}
+
+#if _FFR_ANY_FREE_FS
+/*
+** FILESYS_FREE -- check whether there is at least one fs with enough space.
+**
+** Parameters:
+** fsize -- file size in bytes
+**
+** Returns:
+** true iff there is one fs with more than fsize bytes free.
+*/
+
+bool
+filesys_free(fsize)
+ long fsize;
+{
+ int i;
+
+ if (fsize <= 0)
+ return true;
+ for (i = 0; i < NumFileSys; ++i)
+ {
+ long needed = 0;
+
+ if (FILE_SYS_AVAIL(i) < 0 || FILE_SYS_BLKSIZE(i) <= 0)
+ continue;
+ needed += fsize / FILE_SYS_BLKSIZE(i)
+ + ((fsize % FILE_SYS_BLKSIZE(i)
+ > 0) ? 1 : 0)
+ + MinBlocksFree;
+ if (needed <= FILE_SYS_AVAIL(i))
+ return true;
+ }
+ return false;
+}
+#endif /* _FFR_ANY_FREE_FS */
+
+/*
+** DISK_STATUS -- show amount of free space in queue directories
+**
+** Parameters:
+** out -- output file pointer.
+** prefix -- string to output in front of each line.
+**
+** Returns:
+** none.
+*/
+
+void
+disk_status(out, prefix)
+ SM_FILE_T *out;
+ char *prefix;
+{
+ int i;
+ long avail, blksize;
+ long free;
+
+ for (i = 0; i < NumFileSys; ++i)
+ {
+ avail = freediskspace(FILE_SYS_NAME(i), &blksize);
+ if (avail >= 0 && blksize > 0)
+ {
+ free = (long)((double) avail *
+ ((double) blksize / 1024));
+ }
+ else
+ free = -1;
+ (void) sm_io_fprintf(out, SM_TIME_DEFAULT,
+ "%s%d/%s/%ld\r\n",
+ prefix, i,
+ FILE_SYS_NAME(i),
+ free);
+ }
+}
+
+#if SM_CONF_SHM
+
+/*
+** INIT_SEM -- initialize semaphore system
+**
+** Parameters:
+** owner -- is this the owner of semaphores?
+**
+** Returns:
+** none.
+*/
+
+#if _FFR_USE_SEM_LOCKING
+#if SM_CONF_SEM
+static int SemId = -1; /* Semaphore Id */
+int SemKey = SM_SEM_KEY;
+#endif /* SM_CONF_SEM */
+#endif /* _FFR_USE_SEM_LOCKING */
+
+static void init_sem __P((bool));
+
+static void
+init_sem(owner)
+ bool owner;
+{
+#if _FFR_USE_SEM_LOCKING
+#if SM_CONF_SEM
+ SemId = sm_sem_start(SemKey, 1, 0, owner);
+ if (SemId < 0)
+ {
+ sm_syslog(LOG_ERR, NOQID,
+ "func=init_sem, sem_key=%ld, sm_sem_start=%d",
+ (long) SemKey, SemId);
+ return;
+ }
+#endif /* SM_CONF_SEM */
+#endif /* _FFR_USE_SEM_LOCKING */
+ return;
+}
+
+/*
+** STOP_SEM -- stop semaphore system
+**
+** Parameters:
+** owner -- is this the owner of semaphores?
+**
+** Returns:
+** none.
+*/
+
+static void stop_sem __P((bool));
+
+static void
+stop_sem(owner)
+ bool owner;
+{
+#if _FFR_USE_SEM_LOCKING
+#if SM_CONF_SEM
+ if (owner && SemId >= 0)
+ sm_sem_stop(SemId);
+#endif /* SM_CONF_SEM */
+#endif /* _FFR_USE_SEM_LOCKING */
+ return;
+}
+
+/*
+** UPD_QS -- update information about queue when adding/deleting an entry
+**
+** Parameters:
+** e -- envelope.
+** count -- add/remove entry (+1/0/-1: add/no change/remove)
+** space -- update the space available as well.
+** (>0/0/<0: add/no change/remove)
+** where -- caller (for logging)
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** Modifies available space in filesystem.
+** Changes number of entries in queue directory.
+*/
+
+void
+upd_qs(e, count, space, where)
+ ENVELOPE *e;
+ int count;
+ int space;
+ char *where;
+{
+ short fidx;
+ int idx;
+# if _FFR_USE_SEM_LOCKING
+ int r;
+# endif /* _FFR_USE_SEM_LOCKING */
+ long s;
+
+ if (ShmId == SM_SHM_NO_ID || e == NULL)
+ return;
+ if (e->e_qgrp == NOQGRP || e->e_qdir == NOQDIR)
+ return;
+ idx = Queue[e->e_qgrp]->qg_qpaths[e->e_qdir].qp_idx;
+ if (tTd(73,2))
+ sm_dprintf("func=upd_qs, count=%d, space=%d, where=%s, idx=%d, entries=%d\n",
+ count, space, where, idx, QSHM_ENTRIES(idx));
+
+ /* XXX in theory this needs to be protected with a mutex */
+ if (QSHM_ENTRIES(idx) >= 0 && count != 0)
+ {
+# if _FFR_USE_SEM_LOCKING
+ r = sm_sem_acq(SemId, 0, 1);
+# endif /* _FFR_USE_SEM_LOCKING */
+ QSHM_ENTRIES(idx) += count;
+# if _FFR_USE_SEM_LOCKING
+ if (r >= 0)
+ r = sm_sem_rel(SemId, 0, 1);
+# endif /* _FFR_USE_SEM_LOCKING */
+ }
+
+ fidx = Queue[e->e_qgrp]->qg_qpaths[e->e_qdir].qp_fsysidx;
+ if (fidx < 0)
+ return;
+
+ /* update available space also? (might be loseqfile) */
+ if (space == 0)
+ return;
+
+ /* convert size to blocks; this causes rounding errors */
+ s = e->e_msgsize / FILE_SYS_BLKSIZE(fidx);
+ if (s == 0)
+ return;
+
+ /* XXX in theory this needs to be protected with a mutex */
+ if (space > 0)
+ FILE_SYS_AVAIL(fidx) += s;
+ else
+ FILE_SYS_AVAIL(fidx) -= s;
+
+}
+
+static bool write_key_file __P((char *, long));
+static long read_key_file __P((char *, long));
+
+/*
+** WRITE_KEY_FILE -- record some key into a file.
+**
+** Parameters:
+** keypath -- file name.
+** key -- key to write.
+**
+** Returns:
+** true iff file could be written.
+**
+** Side Effects:
+** writes file.
+*/
+
+static bool
+write_key_file(keypath, key)
+ char *keypath;
+ long key;
+{
+ bool ok;
+ long sff;
+ SM_FILE_T *keyf;
+
+ ok = false;
+ if (keypath == NULL || *keypath == '\0')
+ return ok;
+ sff = SFF_NOLINK|SFF_ROOTOK|SFF_REGONLY|SFF_CREAT;
+ if (TrustedUid != 0 && RealUid == TrustedUid)
+ sff |= SFF_OPENASROOT;
+ keyf = safefopen(keypath, O_WRONLY|O_TRUNC, FileMode, sff);
+ if (keyf == NULL)
+ {
+ sm_syslog(LOG_ERR, NOQID, "unable to write %s: %s",
+ keypath, sm_errstring(errno));
+ }
+ else
+ {
+ if (geteuid() == 0 && RunAsUid != 0)
+ {
+# if HASFCHOWN
+ int fd;
+
+ fd = keyf->f_file;
+ if (fd >= 0 && fchown(fd, RunAsUid, -1) < 0)
+ {
+ int err = errno;
+
+ sm_syslog(LOG_ALERT, NOQID,
+ "ownership change on %s to %d failed: %s",
+ keypath, RunAsUid, sm_errstring(err));
+ }
+# endif /* HASFCHOWN */
+ }
+ ok = sm_io_fprintf(keyf, SM_TIME_DEFAULT, "%ld\n", key) !=
+ SM_IO_EOF;
+ ok = (sm_io_close(keyf, SM_TIME_DEFAULT) != SM_IO_EOF) && ok;
+ }
+ return ok;
+}
+
+/*
+** READ_KEY_FILE -- read a key from a file.
+**
+** Parameters:
+** keypath -- file name.
+** key -- default key.
+**
+** Returns:
+** key.
+*/
+
+static long
+read_key_file(keypath, key)
+ char *keypath;
+ long key;
+{
+ int r;
+ long sff, n;
+ SM_FILE_T *keyf;
+
+ if (keypath == NULL || *keypath == '\0')
+ return key;
+ sff = SFF_NOLINK|SFF_ROOTOK|SFF_REGONLY;
+ if (RealUid == 0 || (TrustedUid != 0 && RealUid == TrustedUid))
+ sff |= SFF_OPENASROOT;
+ keyf = safefopen(keypath, O_RDONLY, FileMode, sff);
+ if (keyf == NULL)
+ {
+ sm_syslog(LOG_ERR, NOQID, "unable to read %s: %s",
+ keypath, sm_errstring(errno));
+ }
+ else
+ {
+ r = sm_io_fscanf(keyf, SM_TIME_DEFAULT, "%ld", &n);
+ if (r == 1)
+ key = n;
+ (void) sm_io_close(keyf, SM_TIME_DEFAULT);
+ }
+ return key;
+}
+
+/*
+** INIT_SHM -- initialize shared memory structure
+**
+** Initialize or attach to shared memory segment.
+** Currently it is not a fatal error if this doesn't work.
+** However, it causes us to have a "fallback" storage location
+** for everything that is supposed to be in the shared memory,
+** which makes the code slightly ugly.
+**
+** Parameters:
+** qn -- number of queue directories.
+** owner -- owner of shared memory.
+** hash -- identifies data that is stored in shared memory.
+**
+** Returns:
+** none.
+*/
+
+static void init_shm __P((int, bool, unsigned int));
+
+static void
+init_shm(qn, owner, hash)
+ int qn;
+ bool owner;
+ unsigned int hash;
+{
+ int i;
+ int count;
+ int save_errno;
+ bool keyselect;
+
+ PtrFileSys = &FileSys[0];
+ PNumFileSys = &Numfilesys;
+/* if this "key" is specified: select one yourself */
+#define SEL_SHM_KEY ((key_t) -1)
+#define FIRST_SHM_KEY 25
+
+ /* This allows us to disable shared memory at runtime. */
+ if (ShmKey == 0)
+ return;
+
+ count = 0;
+ shms = SM_T_SIZE + qn * sizeof(QUEUE_SHM_T);
+ keyselect = ShmKey == SEL_SHM_KEY;
+ if (keyselect)
+ {
+ if (owner)
+ ShmKey = FIRST_SHM_KEY;
+ else
+ {
+ errno = 0;
+ ShmKey = read_key_file(ShmKeyFile, ShmKey);
+ keyselect = false;
+ if (ShmKey == SEL_SHM_KEY)
+ {
+ save_errno = (errno != 0) ? errno : EINVAL;
+ goto error;
+ }
+ }
+ }
+ for (;;)
+ {
+ /* allow read/write access for group? */
+ Pshm = sm_shmstart(ShmKey, shms,
+ SHM_R|SHM_W|(SHM_R>>3)|(SHM_W>>3),
+ &ShmId, owner);
+ save_errno = errno;
+ if (Pshm != NULL || !sm_file_exists(save_errno))
+ break;
+ if (++count >= 3)
+ {
+ if (keyselect)
+ {
+ ++ShmKey;
+
+ /* back where we started? */
+ if (ShmKey == SEL_SHM_KEY)
+ break;
+ continue;
+ }
+ break;
+ }
+
+ /* only sleep if we are at the first key */
+ if (!keyselect || ShmKey == SEL_SHM_KEY)
+ sleep(count);
+ }
+ if (Pshm != NULL)
+ {
+ int *p;
+
+ if (keyselect)
+ (void) write_key_file(ShmKeyFile, (long) ShmKey);
+ if (owner && RunAsUid != 0)
+ {
+ i = sm_shmsetowner(ShmId, RunAsUid, RunAsGid, 0660);
+ if (i != 0)
+ sm_syslog(LOG_ERR, NOQID,
+ "key=%ld, sm_shmsetowner=%d, RunAsUid=%d, RunAsGid=%d",
+ (long) ShmKey, i, RunAsUid, RunAsGid);
+ }
+ p = (int *) Pshm;
+ if (owner)
+ {
+ *p = (int) shms;
+ *((pid_t *) SHM_OFF_PID(Pshm)) = CurrentPid;
+ p = (int *) SHM_OFF_TAG(Pshm);
+ *p = hash;
+ }
+ else
+ {
+ if (*p != (int) shms)
+ {
+ save_errno = EINVAL;
+ cleanup_shm(false);
+ goto error;
+ }
+ p = (int *) SHM_OFF_TAG(Pshm);
+ if (*p != (int) hash)
+ {
+ save_errno = EINVAL;
+ cleanup_shm(false);
+ goto error;
+ }
+
+ /*
+ ** XXX how to check the pid?
+ ** Read it from the pid-file? That does
+ ** not need to exist.
+ ** We could disable shm if we can't confirm
+ ** that it is the right one.
+ */
+ }
+
+ PtrFileSys = (FILESYS *) OFF_FILE_SYS(Pshm);
+ PNumFileSys = (int *) OFF_NUM_FILE_SYS(Pshm);
+ QShm = (QUEUE_SHM_T *) OFF_QUEUE_SHM(Pshm);
+ PRSATmpCnt = (int *) OFF_RSA_TMP_CNT(Pshm);
+ *PRSATmpCnt = 0;
+ if (owner)
+ {
+ /* initialize values in shared memory */
+ NumFileSys = 0;
+ for (i = 0; i < qn; i++)
+ QShm[i].qs_entries = -1;
+ }
+ init_sem(owner);
+ return;
+ }
+ error:
+ if (LogLevel > (owner ? 8 : 11))
+ {
+ sm_syslog(owner ? LOG_ERR : LOG_NOTICE, NOQID,
+ "can't %s shared memory, key=%ld: %s",
+ owner ? "initialize" : "attach to",
+ (long) ShmKey, sm_errstring(save_errno));
+ }
+}
+#endif /* SM_CONF_SHM */
+
+
+/*
+** SETUP_QUEUES -- set up all queue groups
+**
+** Parameters:
+** owner -- owner of shared memory?
+**
+** Returns:
+** none.
+**
+#if SM_CONF_SHM
+** Side Effects:
+** attaches shared memory.
+#endif * SM_CONF_SHM *
+*/
+
+void
+setup_queues(owner)
+ bool owner;
+{
+ int i, qn, len;
+ unsigned int hashval;
+ time_t now;
+ char basedir[MAXPATHLEN];
+ struct stat st;
+
+ /*
+ ** Determine basedir for all queue directories.
+ ** All queue directories must be (first level) subdirectories
+ ** of the basedir. The basedir is the QueueDir
+ ** without wildcards, but with trailing /
+ */
+
+ hashval = 0;
+ errno = 0;
+ len = sm_strlcpy(basedir, QueueDir, sizeof(basedir));
+
+ /* Provide space for trailing '/' */
+ if (len >= sizeof(basedir) - 1)
+ {
+ syserr("QueueDirectory: path too long: %d, max %d",
+ len, (int) sizeof(basedir) - 1);
+ ExitStat = EX_CONFIG;
+ return;
+ }
+ SM_ASSERT(len > 0);
+ if (basedir[len - 1] == '*')
+ {
+ char *cp;
+
+ cp = SM_LAST_DIR_DELIM(basedir);
+ if (cp == NULL)
+ {
+ syserr("QueueDirectory: can not wildcard relative path \"%s\"",
+ QueueDir);
+ if (tTd(41, 2))
+ sm_dprintf("setup_queues: \"%s\": Can not wildcard relative path.\n",
+ QueueDir);
+ ExitStat = EX_CONFIG;
+ return;
+ }
+
+ /* cut off wildcard pattern */
+ *++cp = '\0';
+ len = cp - basedir;
+ }
+ else if (!SM_IS_DIR_DELIM(basedir[len - 1]))
+ {
+ /* append trailing slash since it is a directory */
+ basedir[len] = '/';
+ basedir[++len] = '\0';
+ }
+
+ /* len counts up to the last directory delimiter */
+ SM_ASSERT(basedir[len - 1] == '/');
+
+ if (chdir(basedir) < 0)
+ {
+ int save_errno = errno;
+
+ syserr("can not chdir(%s)", basedir);
+ if (save_errno == EACCES)
+ (void) sm_io_fprintf(smioerr, SM_TIME_DEFAULT,
+ "Program mode requires special privileges, e.g., root or TrustedUser.\n");
+ if (tTd(41, 2))
+ sm_dprintf("setup_queues: \"%s\": %s\n",
+ basedir, sm_errstring(errno));
+ ExitStat = EX_CONFIG;
+ return;
+ }
+#if SM_CONF_SHM
+ hashval = hash_q(basedir, hashval);
+#endif /* SM_CONF_SHM */
+
+ /* initialize for queue runs */
+ DoQueueRun = false;
+ now = curtime();
+ for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
+ Queue[i]->qg_nextrun = now;
+
+
+ if (UseMSP && OpMode != MD_TEST)
+ {
+ long sff = SFF_CREAT;
+
+ if (stat(".", &st) < 0)
+ {
+ syserr("can not stat(%s)", basedir);
+ if (tTd(41, 2))
+ sm_dprintf("setup_queues: \"%s\": %s\n",
+ basedir, sm_errstring(errno));
+ ExitStat = EX_CONFIG;
+ return;
+ }
+ if (RunAsUid == 0)
+ sff |= SFF_ROOTOK;
+
+ /*
+ ** Check queue directory permissions.
+ ** Can we write to a group writable queue directory?
+ */
+
+ if (bitset(S_IWGRP, QueueFileMode) &&
+ bitset(S_IWGRP, st.st_mode) &&
+ safefile(" ", RunAsUid, RunAsGid, RunAsUserName, sff,
+ QueueFileMode, NULL) != 0)
+ {
+ syserr("can not write to queue directory %s (RunAsGid=%d, required=%d)",
+ basedir, (int) RunAsGid, (int) st.st_gid);
+ }
+ if (bitset(S_IWOTH|S_IXOTH, st.st_mode))
+ {
+#if _FFR_MSP_PARANOIA
+ syserr("dangerous permissions=%o on queue directory %s",
+ (int) st.st_mode, basedir);
+#else /* _FFR_MSP_PARANOIA */
+ if (LogLevel > 0)
+ sm_syslog(LOG_ERR, NOQID,
+ "dangerous permissions=%o on queue directory %s",
+ (int) st.st_mode, basedir);
+#endif /* _FFR_MSP_PARANOIA */
+ }
+#if _FFR_MSP_PARANOIA
+ if (NumQueue > 1)
+ syserr("can not use multiple queues for MSP");
+#endif /* _FFR_MSP_PARANOIA */
+ }
+
+ /* initial number of queue directories */
+ qn = 0;
+ for (i = 0; i < NumQueue && Queue[i] != NULL; i++)
+ qn = multiqueue_cache(basedir, len, Queue[i], qn, &hashval);
+
+#if SM_CONF_SHM
+ init_shm(qn, owner, hashval);
+ i = filesys_setup(owner || ShmId == SM_SHM_NO_ID);
+ if (i == FSF_NOT_FOUND)
+ {
+ /*
+ ** We didn't get the right filesystem data
+ ** This may happen if we don't have the right shared memory.
+ ** So let's do this without shared memory.
+ */
+
+ SM_ASSERT(!owner);
+ cleanup_shm(false); /* release shared memory */
+ i = filesys_setup(false);
+ if (i < 0)
+ syserr("filesys_setup failed twice, result=%d", i);
+ else if (LogLevel > 8)
+ sm_syslog(LOG_WARNING, NOQID,
+ "shared memory does not contain expected data, ignored");
+ }
+#else /* SM_CONF_SHM */
+ i = filesys_setup(true);
+#endif /* SM_CONF_SHM */
+ if (i < 0)
+ ExitStat = EX_CONFIG;
+}
+
+#if SM_CONF_SHM
+/*
+** CLEANUP_SHM -- do some cleanup work for shared memory etc
+**
+** Parameters:
+** owner -- owner of shared memory?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** detaches shared memory.
+*/
+
+void
+cleanup_shm(owner)
+ bool owner;
+{
+ if (ShmId != SM_SHM_NO_ID)
+ {
+ if (sm_shmstop(Pshm, ShmId, owner) < 0 && LogLevel > 8)
+ sm_syslog(LOG_INFO, NOQID, "sm_shmstop failed=%s",
+ sm_errstring(errno));
+ Pshm = NULL;
+ ShmId = SM_SHM_NO_ID;
+ }
+ stop_sem(owner);
+}
+#endif /* SM_CONF_SHM */
+
+/*
+** CLEANUP_QUEUES -- do some cleanup work for queues
+**
+** Parameters:
+** none.
+**
+** Returns:
+** none.
+**
+*/
+
+void
+cleanup_queues()
+{
+ sync_queue_time();
+}
+/*
+** SET_DEF_QUEUEVAL -- set default values for a queue group.
+**
+** Parameters:
+** qg -- queue group
+** all -- set all values (true for default group)?
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** sets default values for the queue group.
+*/
+
+void
+set_def_queueval(qg, all)
+ QUEUEGRP *qg;
+ bool all;
+{
+ if (bitnset(QD_DEFINED, qg->qg_flags))
+ return;
+ if (all)
+ qg->qg_qdir = QueueDir;
+#if _FFR_QUEUE_GROUP_SORTORDER
+ qg->qg_sortorder = QueueSortOrder;
+#endif /* _FFR_QUEUE_GROUP_SORTORDER */
+ qg->qg_maxqrun = all ? MaxRunnersPerQueue : -1;
+ qg->qg_nice = NiceQueueRun;
+}
+/*
+** MAKEQUEUE -- define a new queue.
+**
+** Parameters:
+** line -- description of queue. This is in labeled fields.
+** The fields are:
+** F -- the flags associated with the queue
+** I -- the interval between running the queue
+** J -- the maximum # of jobs in work list
+** [M -- the maximum # of jobs in a queue run]
+** N -- the niceness at which to run
+** P -- the path to the queue
+** S -- the queue sorting order
+** R -- number of parallel queue runners
+** r -- max recipients per envelope
+** The first word is the canonical name of the queue.
+** qdef -- this is a 'Q' definition from .cf
+**
+** Returns:
+** none.
+**
+** Side Effects:
+** enters the queue into the queue table.
+*/
+
+void
+makequeue(line, qdef)
+ char *line;
+ bool qdef;
+{
+ register char *p;
+ register QUEUEGRP *qg;
+ register STAB *s;
+ int i;
+ char fcode;
+
+ /* allocate a queue and set up defaults */
+ qg = (QUEUEGRP *) xalloc(sizeof(*qg));
+ memset((char *) qg, '\0', sizeof(*qg));
+
+ if (line[0] == '\0')
+ {
+ syserr("name required for queue");
+ return;
+ }
+
+ /* collect the queue name */
+ for (p = line;
+ *p != '\0' && *p != ',' && !(isascii(*p) && isspace(*p));
+ p++)
+ continue;
+ if (*p != '\0')
+ *p++ = '\0';
+ qg->qg_name = newstr(line);
+
+ /* set default values, can be overridden below */
+ set_def_queueval(qg, false);
+
+ /* now scan through and assign info from the fields */
+ while (*p != '\0')
+ {
+ auto char *delimptr;
+
+ while (*p != '\0' &&
+ (*p == ',' || (isascii(*p) && isspace(*p))))
+ p++;
+
+ /* p now points to field code */
+ fcode = *p;
+ while (*p != '\0' && *p != '=' && *p != ',')
+ p++;
+ if (*p++ != '=')
+ {
+ syserr("queue %s: `=' expected", qg->qg_name);
+ return;
+ }
+ while (isascii(*p) && isspace(*p))
+ p++;
+
+ /* p now points to the field body */
+ p = munchstring(p, &delimptr, ',');
+
+ /* install the field into the queue struct */
+ switch (fcode)
+ {
+ case 'P': /* pathname */
+ if (*p == '\0')
+ syserr("queue %s: empty path name",
+ qg->qg_name);
+ else
+ qg->qg_qdir = newstr(p);
+ break;
+
+ case 'F': /* flags */
+ for (; *p != '\0'; p++)
+ if (!(isascii(*p) && isspace(*p)))
+ setbitn(*p, qg->qg_flags);
+ break;
+
+ /*
+ ** Do we need two intervals here:
+ ** One for persistent queue runners,
+ ** one for "normal" queue runs?
+ */
+
+ case 'I': /* interval between running the queue */
+ qg->qg_queueintvl = convtime(p, 'm');
+ break;
+
+ case 'N': /* run niceness */
+ qg->qg_nice = atoi(p);
+ break;
+
+ case 'R': /* maximum # of runners for the group */
+ i = atoi(p);
+
+ /* can't have more runners than allowed total */
+ if (MaxQueueChildren > 0 && i > MaxQueueChildren)
+ {
+ qg->qg_maxqrun = MaxQueueChildren;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Q=%s: R=%d exceeds MaxQueueChildren=%d, set to MaxQueueChildren\n",
+ qg->qg_name, i,
+ MaxQueueChildren);
+ }
+ else
+ qg->qg_maxqrun = i;
+ break;
+
+ case 'J': /* maximum # of jobs in work list */
+ qg->qg_maxlist = atoi(p);
+ break;
+
+ case 'r': /* max recipients per envelope */
+ qg->qg_maxrcpt = atoi(p);
+ break;
+
+#if _FFR_QUEUE_GROUP_SORTORDER
+ case 'S': /* queue sorting order */
+ switch (*p)
+ {
+ case 'h': /* Host first */
+ case 'H':
+ qg->qg_sortorder = QSO_BYHOST;
+ break;
+
+ case 'p': /* Priority order */
+ case 'P':
+ qg->qg_sortorder = QSO_BYPRIORITY;
+ break;
+
+ case 't': /* Submission time */
+ case 'T':
+ qg->qg_sortorder = QSO_BYTIME;
+ break;
+
+ case 'f': /* File name */
+ case 'F':
+ qg->qg_sortorder = QSO_BYFILENAME;
+ break;
+
+ case 'm': /* Modification time */
+ case 'M':
+ qg->qg_sortorder = QSO_BYMODTIME;
+ break;
+
+ case 'r': /* Random */
+ case 'R':
+ qg->qg_sortorder = QSO_RANDOM;
+ break;
+
+# if _FFR_RHS
+ case 's': /* Shuffled host name */
+ case 'S':
+ qg->qg_sortorder = QSO_BYSHUFFLE;
+ break;
+# endif /* _FFR_RHS */
+
+ case 'n': /* none */
+ case 'N':
+ qg->qg_sortorder = QSO_NONE;
+ break;
+
+ default:
+ syserr("Invalid queue sort order \"%s\"", p);
+ }
+ break;
+#endif /* _FFR_QUEUE_GROUP_SORTORDER */
+
+ default:
+ syserr("Q%s: unknown queue equate %c=",
+ qg->qg_name, fcode);
+ break;
+ }
+
+ p = delimptr;
+ }
+
+#if !HASNICE
+ if (qg->qg_nice != NiceQueueRun)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Q%s: Warning: N= set on system that doesn't support nice()\n",
+ qg->qg_name);
+ }
+#endif /* !HASNICE */
+
+ /* do some rationality checking */
+ if (NumQueue >= MAXQUEUEGROUPS)
+ {
+ syserr("too many queue groups defined (%d max)",
+ MAXQUEUEGROUPS);
+ return;
+ }
+
+ if (qg->qg_qdir == NULL)
+ {
+ if (QueueDir == NULL || *QueueDir == '\0')
+ {
+ syserr("QueueDir must be defined before queue groups");
+ return;
+ }
+ qg->qg_qdir = newstr(QueueDir);
+ }
+
+ if (qg->qg_maxqrun > 1 && !bitnset(QD_FORK, qg->qg_flags))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Warning: Q=%s: R=%d: multiple queue runners specified\n\tbut flag '%c' is not set\n",
+ qg->qg_name, qg->qg_maxqrun, QD_FORK);
+ }
+
+ /* enter the queue into the symbol table */
+ if (tTd(37, 8))
+ sm_syslog(LOG_INFO, NOQID,
+ "Adding %s to stab, path: %s", qg->qg_name,
+ qg->qg_qdir);
+ s = stab(qg->qg_name, ST_QUEUE, ST_ENTER);
+ if (s->s_quegrp != NULL)
+ {
+ i = s->s_quegrp->qg_index;
+
+ /* XXX what about the pointers inside this struct? */
+ sm_free(s->s_quegrp); /* XXX */
+ }
+ else
+ i = NumQueue++;
+ Queue[i] = s->s_quegrp = qg;
+ qg->qg_index = i;
+
+ /* set default value for max queue runners */
+ if (qg->qg_maxqrun < 0)
+ {
+ if (MaxRunnersPerQueue > 0)
+ qg->qg_maxqrun = MaxRunnersPerQueue;
+ else
+ qg->qg_maxqrun = 1;
+ }
+ if (qdef)
+ setbitn(QD_DEFINED, qg->qg_flags);
+}
+#if 0
+/*
+** HASHFQN -- calculate a hash value for a fully qualified host name
+**
+** Arguments:
+** fqn -- an all lower-case host.domain string
+** buckets -- the number of buckets (queue directories)
+**
+** Returns:
+** a bucket number (signed integer)
+** -1 on error
+**
+** Contributed by Exactis.com, Inc.
+*/
+
+int
+hashfqn(fqn, buckets)
+ register char *fqn;
+ int buckets;
+{
+ register char *p;
+ register int h = 0, hash, cnt;
+
+ if (fqn == NULL)
+ return -1;
+
+ /*
+ ** A variation on the gdb hash
+ ** This is the best as of Feb 19, 1996 --bcx
+ */
+
+ p = fqn;
+ h = 0x238F13AF * strlen(p);
+ for (cnt = 0; *p != 0; ++p, cnt++)
+ {
+ h = (h + (*p << (cnt * 5 % 24))) & 0x7FFFFFFF;
+ }
+ h = (1103515243 * h + 12345) & 0x7FFFFFFF;
+ if (buckets < 2)
+ hash = 0;
+ else
+ hash = (h % buckets);
+
+ return hash;
+}
+#endif /* 0 */
+
+/*
+** A structure for sorting Queue according to maxqrun without
+** screwing up Queue itself.
+*/
+
+struct sortqgrp
+{
+ int sg_idx; /* original index */
+ int sg_maxqrun; /* max queue runners */
+};
+typedef struct sortqgrp SORTQGRP_T;
+static int cmpidx __P((const void *, const void *));
+
+static int
+cmpidx(a, b)
+ const void *a;
+ const void *b;
+{
+ /* The sort is highest to lowest, so the comparison is reversed */
+ if (((SORTQGRP_T *)a)->sg_maxqrun < ((SORTQGRP_T *)b)->sg_maxqrun)
+ return 1;
+ else if (((SORTQGRP_T *)a)->sg_maxqrun > ((SORTQGRP_T *)b)->sg_maxqrun)
+ return -1;
+ else
+ return 0;
+}
+
+/*
+** MAKEWORKGROUP -- balance queue groups into work groups per MaxQueueChildren
+**
+** Take the now defined queue groups and assign them to work groups.
+** This is done to balance out the number of concurrently active
+** queue runners such that MaxQueueChildren is not exceeded. This may
+** result in more than one queue group per work group. In such a case
+** the number of running queue groups in that work group will have no
+** more than the work group maximum number of runners (a "fair" portion
+** of MaxQueueRunners). All queue groups within a work group will get a
+** chance at running.
+**
+** Parameters:
+** none.
+**
+** Returns:
+** nothing.
+**
+** Side Effects:
+** Sets up WorkGrp structure.
+*/
+
+void
+makeworkgroups()
+{
+ int i, j, total_runners, dir, h;
+ SORTQGRP_T si[MAXQUEUEGROUPS + 1];
+
+ total_runners = 0;
+ if (NumQueue == 1 && strcmp(Queue[0]->qg_name, "mqueue") == 0)
+ {
+ /*
+ ** There is only the "mqueue" queue group (a default)
+ ** containing all of the queues. We want to provide to
+ ** this queue group the maximum allowable queue runners.
+ ** To match older behavior (8.10/8.11) we'll try for
+ ** 1 runner per queue capping it at MaxQueueChildren.
+ ** So if there are N queues, then there will be N runners
+ ** for the "mqueue" queue group (where N is kept less than
+ ** MaxQueueChildren).
+ */
+
+ NumWorkGroups = 1;
+ WorkGrp[0].wg_numqgrp = 1;
+ WorkGrp[0].wg_qgs = (QUEUEGRP **) xalloc(sizeof(QUEUEGRP *));
+ WorkGrp[0].wg_qgs[0] = Queue[0];
+ if (MaxQueueChildren > 0 &&
+ Queue[0]->qg_numqueues > MaxQueueChildren)
+ WorkGrp[0].wg_runners = MaxQueueChildren;
+ else
+ WorkGrp[0].wg_runners = Queue[0]->qg_numqueues;
+
+ Queue[0]->qg_wgrp = 0;
+
+ /* can't have more runners than allowed total */
+ if (MaxQueueChildren > 0 &&
+ Queue[0]->qg_maxqrun > MaxQueueChildren)
+ Queue[0]->qg_maxqrun = MaxQueueChildren;
+ WorkGrp[0].wg_maxact = Queue[0]->qg_maxqrun;
+ WorkGrp[0].wg_lowqintvl = Queue[0]->qg_queueintvl;
+ return;
+ }
+
+ for (i = 0; i < NumQueue; i++)
+ {
+ si[i].sg_maxqrun = Queue[i]->qg_maxqrun;
+ si[i].sg_idx = i;
+ }
+ qsort(si, NumQueue, sizeof(si[0]), cmpidx);
+
+ NumWorkGroups = 0;
+ for (i = 0; i < NumQueue; i++)
+ {
+ total_runners += si[i].sg_maxqrun;
+ if (MaxQueueChildren <= 0 || total_runners <= MaxQueueChildren)
+ NumWorkGroups++;
+ else
+ break;
+ }
+
+ if (NumWorkGroups < 1)
+ NumWorkGroups = 1; /* gotta have one at least */
+ else if (NumWorkGroups > MAXWORKGROUPS)
+ NumWorkGroups = MAXWORKGROUPS; /* the limit */
+
+ /*
+ ** We now know the number of work groups to pack the queue groups
+ ** into. The queue groups in 'Queue' are sorted from highest
+ ** to lowest for the number of runners per queue group.
+ ** We put the queue groups with the largest number of runners
+ ** into work groups first. Then the smaller ones are fitted in
+ ** where it looks best.
+ */
+
+ j = 0;
+ dir = 1;
+ for (i = 0; i < NumQueue; i++)
+ {
+ /* a to-and-fro packing scheme, continue from last position */
+ if (j >= NumWorkGroups)
+ {
+ dir = -1;
+ j = NumWorkGroups - 1;
+ }
+ else if (j < 0)
+ {
+ j = 0;
+ dir = 1;
+ }
+
+ if (WorkGrp[j].wg_qgs == NULL)
+ WorkGrp[j].wg_qgs = (QUEUEGRP **)sm_malloc(sizeof(QUEUEGRP *) *
+ (WorkGrp[j].wg_numqgrp + 1));
+ else
+ WorkGrp[j].wg_qgs = (QUEUEGRP **)sm_realloc(WorkGrp[j].wg_qgs,
+ sizeof(QUEUEGRP *) *
+ (WorkGrp[j].wg_numqgrp + 1));
+ if (WorkGrp[j].wg_qgs == NULL)
+ {
+ syserr("!cannot allocate memory for work queues, need %d bytes",
+ (int) (sizeof(QUEUEGRP *) *
+ (WorkGrp[j].wg_numqgrp + 1)));
+ }
+
+ h = si[i].sg_idx;
+ WorkGrp[j].wg_qgs[WorkGrp[j].wg_numqgrp] = Queue[h];
+ WorkGrp[j].wg_numqgrp++;
+ WorkGrp[j].wg_runners += Queue[h]->qg_maxqrun;
+ Queue[h]->qg_wgrp = j;
+
+ if (WorkGrp[j].wg_maxact == 0)
+ {
+ /* can't have more runners than allowed total */
+ if (MaxQueueChildren > 0 &&
+ Queue[h]->qg_maxqrun > MaxQueueChildren)
+ Queue[h]->qg_maxqrun = MaxQueueChildren;
+ WorkGrp[j].wg_maxact = Queue[h]->qg_maxqrun;
+ }
+
+ /*
+ ** XXX: must wg_lowqintvl be the GCD?
+ ** qg1: 2m, qg2: 3m, minimum: 2m, when do queue runs for
+ ** qg2 occur?
+ */
+
+ /* keep track of the lowest interval for a persistent runner */
+ if (Queue[h]->qg_queueintvl > 0 &&
+ WorkGrp[j].wg_lowqintvl < Queue[h]->qg_queueintvl)
+ WorkGrp[j].wg_lowqintvl = Queue[h]->qg_queueintvl;
+ j += dir;
+ }
+ if (tTd(41, 9))
+ {
+ for (i = 0; i < NumWorkGroups; i++)
+ {
+ sm_dprintf("Workgroup[%d]=", i);
+ for (j = 0; j < WorkGrp[i].wg_numqgrp; j++)
+ {
+ sm_dprintf("%s, ",
+ WorkGrp[i].wg_qgs[j]->qg_name);
+ }
+ sm_dprintf("\n");
+ }
+ }
+}
+
+/*
+** DUP_DF -- duplicate envelope data file
+**
+** Copy the data file from the 'old' envelope to the 'new' envelope
+** in the most efficient way possible.
+**
+** Create a hard link from the 'old' data file to the 'new' data file.
+** If the old and new queue directories are on different file systems,
+** then the new data file link is created in the old queue directory,
+** and the new queue file will contain a 'd' record pointing to the
+** directory containing the new data file.
+**
+** Parameters:
+** old -- old envelope.
+** new -- new envelope.
+**
+** Results:
+** Returns true on success, false on failure.
+**
+** Side Effects:
+** On success, the new data file is created.
+** On fatal failure, EF_FATALERRS is set in old->e_flags.
+*/
+
+static bool dup_df __P((ENVELOPE *, ENVELOPE *));
+
+static bool
+dup_df(old, new)
+ ENVELOPE *old;
+ ENVELOPE *new;
+{
+ int ofs, nfs, r;
+ char opath[MAXPATHLEN];
+ char npath[MAXPATHLEN];
+
+ if (!bitset(EF_HAS_DF, old->e_flags))
+ {
+ /*
+ ** this can happen if: SuperSafe != True
+ ** and a bounce mail is sent that is split.
+ */
+
+ queueup(old, false, true);
+ }
+ SM_REQUIRE(ISVALIDQGRP(old->e_qgrp) && ISVALIDQDIR(old->e_qdir));
+ SM_REQUIRE(ISVALIDQGRP(new->e_qgrp) && ISVALIDQDIR(new->e_qdir));
+
+ (void) sm_strlcpy(opath, queuename(old, DATAFL_LETTER), sizeof(opath));
+ (void) sm_strlcpy(npath, queuename(new, DATAFL_LETTER), sizeof(npath));
+
+ if (old->e_dfp != NULL)
+ {
+ r = sm_io_setinfo(old->e_dfp, SM_BF_COMMIT, NULL);
+ if (r < 0 && errno != EINVAL)
+ {
+ syserr("@can't commit %s", opath);
+ old->e_flags |= EF_FATALERRS;
+ return false;
+ }
+ }
+
+ /*
+ ** Attempt to create a hard link, if we think both old and new
+ ** are on the same file system, otherwise copy the file.
+ **
+ ** Don't waste time attempting a hard link unless old and new
+ ** are on the same file system.
+ */
+
+ SM_REQUIRE(ISVALIDQGRP(old->e_dfqgrp) && ISVALIDQDIR(old->e_dfqdir));
+ SM_REQUIRE(ISVALIDQGRP(new->e_dfqgrp) && ISVALIDQDIR(new->e_dfqdir));
+
+ ofs = Queue[old->e_dfqgrp]->qg_qpaths[old->e_dfqdir].qp_fsysidx;
+ nfs = Queue[new->e_dfqgrp]->qg_qpaths[new->e_dfqdir].qp_fsysidx;
+ if (FILE_SYS_DEV(ofs) == FILE_SYS_DEV(nfs))
+ {
+ if (link(opath, npath) == 0)
+ {
+ new->e_flags |= EF_HAS_DF;
+ SYNC_DIR(npath, true);
+ return true;
+ }
+ goto error;
+ }
+
+ /*
+ ** Can't link across queue directories, so try to create a hard
+ ** link in the same queue directory as the old df file.
+ ** The qf file will refer to the new df file using a 'd' record.
+ */
+
+ new->e_dfqgrp = old->e_dfqgrp;
+ new->e_dfqdir = old->e_dfqdir;
+ (void) sm_strlcpy(npath, queuename(new, DATAFL_LETTER), sizeof(npath));
+ if (link(opath, npath) == 0)
+ {
+ new->e_flags |= EF_HAS_DF;
+ SYNC_DIR(npath, true);
+ return true;
+ }
+
+ error:
+ if (LogLevel > 0)
+ sm_syslog(LOG_ERR, old->e_id,
+ "dup_df: can't link %s to %s, error=%s, envelope splitting failed",
+ opath, npath, sm_errstring(errno));
+ return false;
+}
+
+/*
+** SPLIT_ENV -- Allocate a new envelope based on a given envelope.
+**
+** Parameters:
+** e -- envelope.
+** sendqueue -- sendqueue for new envelope.
+** qgrp -- index of queue group.
+** qdir -- queue directory.
+**
+** Results:
+** new envelope.
+**
+*/
+
+static ENVELOPE *split_env __P((ENVELOPE *, ADDRESS *, int, int));
+
+static ENVELOPE *
+split_env(e, sendqueue, qgrp, qdir)
+ ENVELOPE *e;
+ ADDRESS *sendqueue;
+ int qgrp;
+ int qdir;
+{
+ ENVELOPE *ee;
+
+ ee = (ENVELOPE *) sm_rpool_malloc_x(e->e_rpool, sizeof(*ee));
+ STRUCTCOPY(*e, *ee);
+ ee->e_message = NULL; /* XXX use original message? */
+ ee->e_id = NULL;
+ assign_queueid(ee);
+ ee->e_sendqueue = sendqueue;
+ ee->e_flags &= ~(EF_INQUEUE|EF_CLRQUEUE|EF_FATALERRS
+ |EF_SENDRECEIPT|EF_RET_PARAM|EF_HAS_DF);
+ ee->e_flags |= EF_NORECEIPT; /* XXX really? */
+ ee->e_from.q_state = QS_SENDER;
+ ee->e_dfp = NULL;
+ ee->e_lockfp = NULL;
+ if (e->e_xfp != NULL)
+ ee->e_xfp = sm_io_dup(e->e_xfp);
+
+ /* failed to dup e->e_xfp, start a new transcript */
+ if (ee->e_xfp == NULL)
+ openxscript(ee);
+
+ ee->e_qgrp = ee->e_dfqgrp = qgrp;
+ ee->e_qdir = ee->e_dfqdir = qdir;
+ ee->e_errormode = EM_MAIL;
+ ee->e_statmsg = NULL;
+ if (e->e_quarmsg != NULL)
+ ee->e_quarmsg = sm_rpool_strdup_x(ee->e_rpool,
+ e->e_quarmsg);
+
+ /*
+ ** XXX Not sure if this copying is necessary.
+ ** sendall() does this copying, but I (dm) don't know if that is
+ ** because of the storage management discipline we were using
+ ** before rpools were introduced, or if it is because these lists
+ ** can be modified later.
+ */
+
+ ee->e_header = copyheader(e->e_header, ee->e_rpool);
+ ee->e_errorqueue = copyqueue(e->e_errorqueue, ee->e_rpool);
+
+ return ee;
+}
+
+/* return values from split functions, check also below! */
+#define SM_SPLIT_FAIL (0)
+#define SM_SPLIT_NONE (1)
+#define SM_SPLIT_NEW(n) (1 + (n))
+
+/*
+** SPLIT_ACROSS_QUEUE_GROUPS
+**
+** This function splits an envelope across multiple queue groups
+** based on the queue group of each recipient.
+**
+** Parameters:
+** e -- envelope.
+**
+** Results:
+** SM_SPLIT_FAIL on failure
+** SM_SPLIT_NONE if no splitting occurred,
+** or 1 + the number of additional envelopes created.
+**
+** Side Effects:
+** On success, e->e_sibling points to a list of zero or more
+** additional envelopes, and the associated data files exist
+** on disk. But the queue files are not created.
+**
+** On failure, e->e_sibling is not changed.
+** The order of recipients in e->e_sendqueue is permuted.
+** Abandoned data files for additional envelopes that failed
+** to be created may exist on disk.
+*/
+
+static int q_qgrp_compare __P((const void *, const void *));
+static int e_filesys_compare __P((const void *, const void *));
+
+static int
+q_qgrp_compare(p1, p2)
+ const void *p1;
+ const void *p2;
+{
+ ADDRESS **pq1 = (ADDRESS **) p1;
+ ADDRESS **pq2 = (ADDRESS **) p2;
+
+ return (*pq1)->q_qgrp - (*pq2)->q_qgrp;
+}
+
+static int
+e_filesys_compare(p1, p2)
+ const void *p1;
+ const void *p2;
+{
+ ENVELOPE **pe1 = (ENVELOPE **) p1;
+ ENVELOPE **pe2 = (ENVELOPE **) p2;
+ int fs1, fs2;
+
+ fs1 = Queue[(*pe1)->e_qgrp]->qg_qpaths[(*pe1)->e_qdir].qp_fsysidx;
+ fs2 = Queue[(*pe2)->e_qgrp]->qg_qpaths[(*pe2)->e_qdir].qp_fsysidx;
+ if (FILE_SYS_DEV(fs1) < FILE_SYS_DEV(fs2))
+ return -1;
+ if (FILE_SYS_DEV(fs1) > FILE_SYS_DEV(fs2))
+ return 1;
+ return 0;
+}
+
+static int split_across_queue_groups __P((ENVELOPE *));
+static int
+split_across_queue_groups(e)
+ ENVELOPE *e;
+{
+ int naddrs, nsplits, i;
+ bool changed;
+ char **pvp;
+ ADDRESS *q, **addrs;
+ ENVELOPE *ee, *es;
+ ENVELOPE *splits[MAXQUEUEGROUPS];
+ char pvpbuf[PSBUFSIZE];
+
+ SM_REQUIRE(ISVALIDQGRP(e->e_qgrp));
+
+ /* Count addresses and assign queue groups. */
+ naddrs = 0;
+ changed = false;
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ ++naddrs;
+
+ /* bad addresses and those already sent stay put */
+ if (QS_IS_BADADDR(q->q_state) ||
+ QS_IS_SENT(q->q_state))
+ q->q_qgrp = e->e_qgrp;
+ else if (!ISVALIDQGRP(q->q_qgrp))
+ {
+ /* call ruleset which should return a queue group */
+ i = rscap(RS_QUEUEGROUP, q->q_user, NULL, e, &pvp,
+ pvpbuf, sizeof(pvpbuf));
+ if (i == EX_OK &&
+ pvp != NULL && pvp[0] != NULL &&
+ (pvp[0][0] & 0377) == CANONNET &&
+ pvp[1] != NULL && pvp[1][0] != '\0')
+ {
+ i = name2qid(pvp[1]);
+ if (ISVALIDQGRP(i))
+ {
+ q->q_qgrp = i;
+ changed = true;
+ if (tTd(20, 4))
+ sm_syslog(LOG_INFO, NOQID,
+ "queue group name %s -> %d",
+ pvp[1], i);
+ continue;
+ }
+ else if (LogLevel > 10)
+ sm_syslog(LOG_INFO, NOQID,
+ "can't find queue group name %s, selection ignored",
+ pvp[1]);
+ }
+ if (q->q_mailer != NULL &&
+ ISVALIDQGRP(q->q_mailer->m_qgrp))
+ {
+ changed = true;
+ q->q_qgrp = q->q_mailer->m_qgrp;
+ }
+ else if (ISVALIDQGRP(e->e_qgrp))
+ q->q_qgrp = e->e_qgrp;
+ else
+ q->q_qgrp = 0;
+ }
+ }
+
+ /* only one address? nothing to split. */
+ if (naddrs <= 1 && !changed)
+ return SM_SPLIT_NONE;
+
+ /* sort the addresses by queue group */
+ addrs = sm_rpool_malloc_x(e->e_rpool, naddrs * sizeof(ADDRESS *));
+ for (i = 0, q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ addrs[i++] = q;
+ }
+ qsort(addrs, naddrs, sizeof(ADDRESS *), q_qgrp_compare);
+
+ /* split into multiple envelopes, by queue group */
+ nsplits = 0;
+ es = NULL;
+ e->e_sendqueue = NULL;
+ for (i = 0; i < naddrs; ++i)
+ {
+ if (i == naddrs - 1 || addrs[i]->q_qgrp != addrs[i + 1]->q_qgrp)
+ addrs[i]->q_next = NULL;
+ else
+ addrs[i]->q_next = addrs[i + 1];
+
+ /* same queue group as original envelope? */
+ if (addrs[i]->q_qgrp == e->e_qgrp)
+ {
+ if (e->e_sendqueue == NULL)
+ e->e_sendqueue = addrs[i];
+ continue;
+ }
+
+ /* different queue group than original envelope */
+ if (es == NULL || addrs[i]->q_qgrp != es->e_qgrp)
+ {
+ ee = split_env(e, addrs[i], addrs[i]->q_qgrp, NOQDIR);
+ es = ee;
+ splits[nsplits++] = ee;
+ }
+ }
+
+ /* no splits? return right now. */
+ if (nsplits <= 0)
+ return SM_SPLIT_NONE;
+
+ /* assign a queue directory to each additional envelope */
+ for (i = 0; i < nsplits; ++i)
+ {
+ es = splits[i];
+#if 0
+ es->e_qdir = pickqdir(Queue[es->e_qgrp], es->e_msgsize, es);
+#endif /* 0 */
+ if (!setnewqueue(es))
+ goto failure;
+ }
+
+ /* sort the additional envelopes by queue file system */
+ qsort(splits, nsplits, sizeof(ENVELOPE *), e_filesys_compare);
+
+ /* create data files for each additional envelope */
+ if (!dup_df(e, splits[0]))
+ {
+ i = 0;
+ goto failure;
+ }
+ for (i = 1; i < nsplits; ++i)
+ {
+ /* copy or link to the previous data file */
+ if (!dup_df(splits[i - 1], splits[i]))
+ goto failure;
+ }
+
+ /* success: prepend the new envelopes to the e->e_sibling list */
+ for (i = 0; i < nsplits; ++i)
+ {
+ es = splits[i];
+ es->e_sibling = e->e_sibling;
+ e->e_sibling = es;
+ }
+ return SM_SPLIT_NEW(nsplits);
+
+ /* failure: clean up */
+ failure:
+ if (i > 0)
+ {
+ int j;
+
+ for (j = 0; j < i; j++)
+ (void) unlink(queuename(splits[j], DATAFL_LETTER));
+ }
+ e->e_sendqueue = addrs[0];
+ for (i = 0; i < naddrs - 1; ++i)
+ addrs[i]->q_next = addrs[i + 1];
+ addrs[naddrs - 1]->q_next = NULL;
+ return SM_SPLIT_FAIL;
+}
+
+/*
+** SPLIT_WITHIN_QUEUE
+**
+** Split an envelope with multiple recipients into several
+** envelopes within the same queue directory, if the number of
+** recipients exceeds the limit for the queue group.
+**
+** Parameters:
+** e -- envelope.
+**
+** Results:
+** SM_SPLIT_FAIL on failure
+** SM_SPLIT_NONE if no splitting occurred,
+** or 1 + the number of additional envelopes created.
+*/
+
+#define SPLIT_LOG_LEVEL 8
+
+static int split_within_queue __P((ENVELOPE *));
+
+static int
+split_within_queue(e)
+ ENVELOPE *e;
+{
+ int maxrcpt, nrcpt, ndead, nsplit, i;
+ int j, l;
+ char *lsplits;
+ ADDRESS *q, **addrs;
+ ENVELOPE *ee, *firstsibling;
+
+ if (!ISVALIDQGRP(e->e_qgrp) || bitset(EF_SPLIT, e->e_flags))
+ return SM_SPLIT_NONE;
+
+ /* don't bother if there is no recipient limit */
+ maxrcpt = Queue[e->e_qgrp]->qg_maxrcpt;
+ if (maxrcpt <= 0)
+ return SM_SPLIT_NONE;
+
+ /* count recipients */
+ nrcpt = 0;
+ for (q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ ++nrcpt;
+ }
+ if (nrcpt <= maxrcpt)
+ return SM_SPLIT_NONE;
+
+ /*
+ ** Preserve the recipient list
+ ** so that we can restore it in case of error.
+ ** (But we discard dead addresses.)
+ */
+
+ addrs = sm_rpool_malloc_x(e->e_rpool, nrcpt * sizeof(ADDRESS *));
+ for (i = 0, q = e->e_sendqueue; q != NULL; q = q->q_next)
+ {
+ if (QS_IS_DEAD(q->q_state))
+ continue;
+ addrs[i++] = q;
+ }
+
+ /*
+ ** Partition the recipient list so that bad and sent addresses
+ ** come first. These will go with the original envelope, and
+ ** do not count towards the maxrcpt limit.
+ ** addrs[] does not contain QS_IS_DEAD() addresses.
+ */
+
+ ndead = 0;
+ for (i = 0; i < nrcpt; ++i)
+ {
+ if (QS_IS_BADADDR(addrs[i]->q_state) ||
+ QS_IS_SENT(addrs[i]->q_state) ||
+ QS_IS_DEAD(addrs[i]->q_state)) /* for paranoia's sake */
+ {
+ if (i > ndead)
+ {
+ ADDRESS *tmp = addrs[i];
+
+ addrs[i] = addrs[ndead];
+ addrs[ndead] = tmp;
+ }
+ ++ndead;
+ }
+ }
+
+ /* Check if no splitting required. */
+ if (nrcpt - ndead <= maxrcpt)
+ return SM_SPLIT_NONE;
+
+ /* fix links */
+ for (i = 0; i < nrcpt - 1; ++i)
+ addrs[i]->q_next = addrs[i + 1];
+ addrs[nrcpt - 1]->q_next = NULL;
+ e->e_sendqueue = addrs[0];
+
+ /* prepare buffer for logging */
+ if (LogLevel > SPLIT_LOG_LEVEL)
+ {
+ l = MAXLINE;
+ lsplits = sm_malloc(l);
+ if (lsplits != NULL)
+ *lsplits = '\0';
+ j = 0;
+ }
+ else
+ {
+ /* get rid of stupid compiler warnings */
+ lsplits = NULL;
+ j = l = 0;
+ }
+
+ /* split the envelope */
+ firstsibling = e->e_sibling;
+ i = maxrcpt + ndead;
+ nsplit = 0;
+ for (;;)
+ {
+ addrs[i - 1]->q_next = NULL;
+ ee = split_env(e, addrs[i], e->e_qgrp, e->e_qdir);
+ if (!dup_df(e, ee))
+ {
+
+ ee = firstsibling;
+ while (ee != NULL)
+ {
+ (void) unlink(queuename(ee, DATAFL_LETTER));
+ ee = ee->e_sibling;
+ }
+
+ /* Error. Restore e's sibling & recipient lists. */
+ e->e_sibling = firstsibling;
+ for (i = 0; i < nrcpt - 1; ++i)
+ addrs[i]->q_next = addrs[i + 1];
+ if (lsplits != NULL)
+ sm_free(lsplits);
+ return SM_SPLIT_FAIL;
+ }
+
+ /* prepend the new envelope to e->e_sibling */
+ ee->e_sibling = e->e_sibling;
+ e->e_sibling = ee;
+ ++nsplit;
+ if (LogLevel > SPLIT_LOG_LEVEL && lsplits != NULL)
+ {
+ if (j >= l - strlen(ee->e_id) - 3)
+ {
+ char *p;
+
+ l += MAXLINE;
+ p = sm_realloc(lsplits, l);
+ if (p == NULL)
+ {
+ /* let's try to get this done */
+ sm_free(lsplits);
+ lsplits = NULL;
+ }
+ else
+ lsplits = p;
+ }
+ if (lsplits != NULL)
+ {
+ if (j == 0)
+ j += sm_strlcat(lsplits + j,
+ ee->e_id,
+ l - j);
+ else
+ j += sm_strlcat2(lsplits + j,
+ "; ",
+ ee->e_id,
+ l - j);
+ SM_ASSERT(j < l);
+ }
+ }
+ if (nrcpt - i <= maxrcpt)
+ break;
+ i += maxrcpt;
+ }
+ if (LogLevel > SPLIT_LOG_LEVEL && lsplits != NULL)
+ {
+ if (nsplit > 0)
+ {
+ sm_syslog(LOG_NOTICE, e->e_id,
+ "split: maxrcpts=%d, rcpts=%d, count=%d, id%s=%s",
+ maxrcpt, nrcpt - ndead, nsplit,
+ nsplit > 1 ? "s" : "", lsplits);
+ }
+ sm_free(lsplits);
+ }
+ return SM_SPLIT_NEW(nsplit);
+}
+/*
+** SPLIT_BY_RECIPIENT
+**
+** Split an envelope with multiple recipients into multiple
+** envelopes as required by the sendmail configuration.
+**
+** Parameters:
+** e -- envelope.
+**
+** Results:
+** Returns true on success, false on failure.
+**
+** Side Effects:
+** see split_across_queue_groups(), split_within_queue(e)
+*/
+
+bool
+split_by_recipient(e)
+ ENVELOPE *e;
+{
+ int split, n, i, j, l;
+ char *lsplits;
+ ENVELOPE *ee, *next, *firstsibling;
+
+ if (OpMode == SM_VERIFY || !ISVALIDQGRP(e->e_qgrp) ||
+ bitset(EF_SPLIT, e->e_flags))
+ return true;
+ n = split_across_queue_groups(e);
+ if (n == SM_SPLIT_FAIL)
+ return false;
+ firstsibling = ee = e->e_sibling;
+ if (n > 1 && LogLevel > SPLIT_LOG_LEVEL)
+ {
+ l = MAXLINE;
+ lsplits = sm_malloc(l);
+ if (lsplits != NULL)
+ *lsplits = '\0';
+ j = 0;
+ }
+ else
+ {
+ /* get rid of stupid compiler warnings */
+ lsplits = NULL;
+ j = l = 0;
+ }
+ for (i = 1; i < n; ++i)
+ {
+ next = ee->e_sibling;
+ if (split_within_queue(ee) == SM_SPLIT_FAIL)
+ {
+ e->e_sibling = firstsibling;
+ return false;
+ }
+ ee->e_flags |= EF_SPLIT;
+ if (LogLevel > SPLIT_LOG_LEVEL && lsplits != NULL)
+ {
+ if (j >= l - strlen(ee->e_id) - 3)
+ {
+ char *p;
+
+ l += MAXLINE;
+ p = sm_realloc(lsplits, l);
+ if (p == NULL)
+ {
+ /* let's try to get this done */
+ sm_free(lsplits);
+ lsplits = NULL;
+ }
+ else
+ lsplits = p;
+ }
+ if (lsplits != NULL)
+ {
+ if (j == 0)
+ j += sm_strlcat(lsplits + j,
+ ee->e_id, l - j);
+ else
+ j += sm_strlcat2(lsplits + j, "; ",
+ ee->e_id, l - j);
+ SM_ASSERT(j < l);
+ }
+ }
+ ee = next;
+ }
+ if (LogLevel > SPLIT_LOG_LEVEL && lsplits != NULL && n > 1)
+ {
+ sm_syslog(LOG_NOTICE, e->e_id, "split: count=%d, id%s=%s",
+ n - 1, n > 2 ? "s" : "", lsplits);
+ sm_free(lsplits);
+ }
+ split = split_within_queue(e) != SM_SPLIT_FAIL;
+ if (split)
+ e->e_flags |= EF_SPLIT;
+ return split;
+}
+
+/*
+** QUARANTINE_QUEUE_ITEM -- {un,}quarantine a single envelope
+**
+** Add/remove quarantine reason and requeue appropriately.
+**
+** Parameters:
+** qgrp -- queue group for the item
+** qdir -- queue directory in the given queue group
+** e -- envelope information for the item
+** reason -- quarantine reason, NULL means unquarantine.
+**
+** Results:
+** true if item changed, false otherwise
+**
+** Side Effects:
+** Changes quarantine tag in queue file and renames it.
+*/
+
+static bool
+quarantine_queue_item(qgrp, qdir, e, reason)
+ int qgrp;
+ int qdir;
+ ENVELOPE *e;
+ char *reason;
+{
+ bool dirty = false;
+ bool failing = false;
+ bool foundq = false;
+ bool finished = false;
+ int fd;
+ int flags;
+ int oldtype;
+ int newtype;
+ int save_errno;
+ MODE_T oldumask = 0;
+ SM_FILE_T *oldqfp, *tempqfp;
+ char *bp;
+ int bufsize;
+ char oldqf[MAXPATHLEN];
+ char tempqf[MAXPATHLEN];
+ char newqf[MAXPATHLEN];
+ char buf[MAXLINE];
+
+ oldtype = queue_letter(e, ANYQFL_LETTER);
+ (void) sm_strlcpy(oldqf, queuename(e, ANYQFL_LETTER), sizeof(oldqf));
+ (void) sm_strlcpy(tempqf, queuename(e, NEWQFL_LETTER), sizeof(tempqf));
+
+ /*
+ ** Instead of duplicating all the open
+ ** and lock code here, tell readqf() to
+ ** do that work and return the open
+ ** file pointer in e_lockfp. Note that
+ ** we must release the locks properly when
+ ** we are done.
+ */
+
+ if (!readqf(e, true))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s\n", qid_printname(e));
+ return false;
+ }
+ oldqfp = e->e_lockfp;
+
+ /* open the new queue file */
+ flags = O_CREAT|O_WRONLY|O_EXCL;
+ if (bitset(S_IWGRP, QueueFileMode))
+ oldumask = umask(002);
+ fd = open(tempqf, flags, QueueFileMode);
+ if (bitset(S_IWGRP, QueueFileMode))
+ (void) umask(oldumask);
+ RELEASE_QUEUE;
+
+ if (fd < 0)
+ {
+ save_errno = errno;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Could not open %s: %s\n",
+ qid_printname(e), tempqf,
+ sm_errstring(save_errno));
+ (void) sm_io_close(oldqfp, SM_TIME_DEFAULT);
+ return false;
+ }
+ if (!lockfile(fd, tempqf, NULL, LOCK_EX|LOCK_NB))
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Could not lock %s\n",
+ qid_printname(e), tempqf);
+ (void) close(fd);
+ (void) sm_io_close(oldqfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+ tempqfp = sm_io_open(SmFtStdiofd, SM_TIME_DEFAULT, (void *) &fd,
+ SM_IO_WRONLY_B, NULL);
+ if (tempqfp == NULL)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Could not lock %s\n",
+ qid_printname(e), tempqf);
+ (void) close(fd);
+ (void) sm_io_close(oldqfp, SM_TIME_DEFAULT);
+ return false;
+ }
+
+ /* Copy the data over, changing the quarantine reason */
+ while (bufsize = sizeof(buf),
+ (bp = fgetfolded(buf, &bufsize, oldqfp)) != NULL)
+ {
+ if (tTd(40, 4))
+ sm_dprintf("+++++ %s\n", bp);
+ switch (bp[0])
+ {
+ case 'q': /* quarantine reason */
+ foundq = true;
+ if (reason == NULL)
+ {
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%s: Removed quarantine of \"%s\"\n",
+ e->e_id, &bp[1]);
+ }
+ sm_syslog(LOG_INFO, e->e_id, "unquarantine");
+ dirty = true;
+ }
+ else if (strcmp(reason, &bp[1]) == 0)
+ {
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%s: Already quarantined with \"%s\"\n",
+ e->e_id, reason);
+ }
+ (void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
+ "q%s\n", reason);
+ }
+ else
+ {
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%s: Quarantine changed from \"%s\" to \"%s\"\n",
+ e->e_id, &bp[1],
+ reason);
+ }
+ (void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
+ "q%s\n", reason);
+ sm_syslog(LOG_INFO, e->e_id, "quarantine=%s",
+ reason);
+ dirty = true;
+ }
+ break;
+
+ case 'S':
+ /*
+ ** If we are quarantining an unquarantined item,
+ ** need to put in a new 'q' line before it's
+ ** too late.
+ */
+
+ if (!foundq && reason != NULL)
+ {
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT,
+ "%s: Quarantined with \"%s\"\n",
+ e->e_id, reason);
+ }
+ (void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
+ "q%s\n", reason);
+ sm_syslog(LOG_INFO, e->e_id, "quarantine=%s",
+ reason);
+ foundq = true;
+ dirty = true;
+ }
+
+ /* Copy the line to the new file */
+ (void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
+ "%s\n", bp);
+ break;
+
+ case '.':
+ finished = true;
+ /* FALLTHROUGH */
+
+ default:
+ /* Copy the line to the new file */
+ (void) sm_io_fprintf(tempqfp, SM_TIME_DEFAULT,
+ "%s\n", bp);
+ break;
+ }
+ if (bp != buf)
+ sm_free(bp);
+ }
+
+ /* Make sure we read the whole old file */
+ errno = sm_io_error(tempqfp);
+ if (errno != 0 && errno != SM_IO_EOF)
+ {
+ save_errno = errno;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Error reading %s: %s\n",
+ qid_printname(e), oldqf,
+ sm_errstring(save_errno));
+ failing = true;
+ }
+
+ if (!failing && !finished)
+ {
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Incomplete file: %s\n",
+ qid_printname(e), oldqf);
+ failing = true;
+ }
+
+ /* Check if we actually changed anything or we can just bail now */
+ if (!dirty)
+ {
+ /* pretend we failed, even though we technically didn't */
+ failing = true;
+ }
+
+ /* Make sure we wrote things out safely */
+ if (!failing &&
+ (sm_io_flush(tempqfp, SM_TIME_DEFAULT) != 0 ||
+ ((SuperSafe == SAFE_REALLY ||
+ SuperSafe == SAFE_REALLY_POSTMILTER ||
+ SuperSafe == SAFE_INTERACTIVE) &&
+ fsync(sm_io_getinfo(tempqfp, SM_IO_WHAT_FD, NULL)) < 0) ||
+ ((errno = sm_io_error(tempqfp)) != 0)))
+ {
+ save_errno = errno;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Skipping %s: Error writing %s: %s\n",
+ qid_printname(e), tempqf,
+ sm_errstring(save_errno));
+ failing = true;
+ }
+
+
+ /* Figure out the new filename */
+ newtype = (reason == NULL ? NORMQF_LETTER : QUARQF_LETTER);
+ if (oldtype == newtype)
+ {
+ /* going to rename tempqf to oldqf */
+ (void) sm_strlcpy(newqf, oldqf, sizeof(newqf));
+ }
+ else
+ {
+ /* going to rename tempqf to new name based on newtype */
+ (void) sm_strlcpy(newqf, queuename(e, newtype), sizeof(newqf));
+ }
+
+ save_errno = 0;
+
+ /* rename tempqf to newqf */
+ if (!failing &&
+ rename(tempqf, newqf) < 0)
+ save_errno = (errno == 0) ? EINVAL : errno;
+
+ /* Check rename() success */
+ if (!failing && save_errno != 0)
+ {
+ sm_syslog(LOG_DEBUG, e->e_id,
+ "quarantine_queue_item: rename(%s, %s): %s",
+ tempqf, newqf, sm_errstring(save_errno));
+
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Error renaming %s to %s: %s\n",
+ tempqf, newqf,
+ sm_errstring(save_errno));
+ if (oldtype == newtype)
+ {
+ /*
+ ** Bail here since we don't know the state of
+ ** the filesystem and may need to keep tempqf
+ ** for the user to rescue us.
+ */
+
+ RELEASE_QUEUE;
+ errno = save_errno;
+ syserr("!452 Error renaming control file %s", tempqf);
+ /* NOTREACHED */
+ }
+ else
+ {
+ /* remove new file (if rename() half completed) */
+ if (xunlink(newqf) < 0)
+ {
+ save_errno = errno;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Error removing %s: %s\n",
+ newqf,
+ sm_errstring(save_errno));
+ }
+
+ /* tempqf removed below */
+ failing = true;
+ }
+
+ }
+
+ /* If changing file types, need to remove old type */
+ if (!failing && oldtype != newtype)
+ {
+ if (xunlink(oldqf) < 0)
+ {
+ save_errno = errno;
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Error removing %s: %s\n",
+ oldqf, sm_errstring(save_errno));
+ }
+ }
+
+ /* see if anything above failed */
+ if (failing)
+ {
+ /* Something failed: remove new file, old file still there */
+ (void) xunlink(tempqf);
+ }
+
+ /*
+ ** fsync() after file operations to make sure metadata is
+ ** written to disk on filesystems in which renames are
+ ** not guaranteed. It's ok if they fail, mail won't be lost.
+ */
+
+ if (SuperSafe != SAFE_NO)
+ {
+ /* for soft-updates */
+ (void) fsync(sm_io_getinfo(tempqfp,
+ SM_IO_WHAT_FD, NULL));
+
+ if (!failing)
+ {
+ /* for soft-updates */
+ (void) fsync(sm_io_getinfo(oldqfp,
+ SM_IO_WHAT_FD, NULL));
+ }
+
+ /* for other odd filesystems */
+ SYNC_DIR(tempqf, false);
+ }
+
+ /* Close up shop */
+ RELEASE_QUEUE;
+ if (tempqfp != NULL)
+ (void) sm_io_close(tempqfp, SM_TIME_DEFAULT);
+ if (oldqfp != NULL)
+ (void) sm_io_close(oldqfp, SM_TIME_DEFAULT);
+
+ /* All went well */
+ return !failing;
+}
+
+/*
+** QUARANTINE_QUEUE -- {un,}quarantine matching items in the queue
+**
+** Read all matching queue items, add/remove quarantine
+** reason, and requeue appropriately.
+**
+** Parameters:
+** reason -- quarantine reason, "." means unquarantine.
+** qgrplimit -- limit to single queue group unless NOQGRP
+**
+** Results:
+** none.
+**
+** Side Effects:
+** Lots of changes to the queue.
+*/
+
+void
+quarantine_queue(reason, qgrplimit)
+ char *reason;
+ int qgrplimit;
+{
+ int changed = 0;
+ int qgrp;
+
+ /* Convert internal representation of unquarantine */
+ if (reason != NULL && reason[0] == '.' && reason[1] == '\0')
+ reason = NULL;
+
+ if (reason != NULL)
+ {
+ /* clean it */
+ reason = newstr(denlstring(reason, true, true));
+ }
+
+ for (qgrp = 0; qgrp < NumQueue && Queue[qgrp] != NULL; qgrp++)
+ {
+ int qdir;
+
+ if (qgrplimit != NOQGRP && qgrplimit != qgrp)
+ continue;
+
+ for (qdir = 0; qdir < Queue[qgrp]->qg_numqueues; qdir++)
+ {
+ int i;
+ int nrequests;
+
+ if (StopRequest)
+ stop_sendmail();
+
+ nrequests = gatherq(qgrp, qdir, true, NULL, NULL);
+
+ /* first see if there is anything */
+ if (nrequests <= 0)
+ {
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT, "%s: no matches\n",
+ qid_printqueue(qgrp, qdir));
+ }
+ continue;
+ }
+
+ if (Verbose)
+ {
+ (void) sm_io_fprintf(smioout,
+ SM_TIME_DEFAULT, "Processing %s:\n",
+ qid_printqueue(qgrp, qdir));
+ }
+
+ for (i = 0; i < WorkListCount; i++)
+ {
+ ENVELOPE e;
+
+ if (StopRequest)
+ stop_sendmail();
+
+ /* setup envelope */
+ clearenvelope(&e, true, sm_rpool_new_x(NULL));
+ e.e_id = WorkList[i].w_name + 2;
+ e.e_qgrp = qgrp;
+ e.e_qdir = qdir;
+
+ if (tTd(70, 101))
+ {
+ sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "Would do %s\n", e.e_id);
+ changed++;
+ }
+ else if (quarantine_queue_item(qgrp, qdir,
+ &e, reason))
+ changed++;
+
+ /* clean up */
+ sm_rpool_free(e.e_rpool);
+ e.e_rpool = NULL;
+ }
+ if (WorkList != NULL)
+ sm_free(WorkList); /* XXX */
+ WorkList = NULL;
+ WorkListSize = 0;
+ WorkListCount = 0;
+ }
+ }
+ if (Verbose)
+ {
+ if (changed == 0)
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "No changes\n");
+ else
+ (void) sm_io_fprintf(smioout, SM_TIME_DEFAULT,
+ "%d change%s\n",
+ changed,
+ changed == 1 ? "" : "s");
+ }
+}
OpenPOWER on IntegriCloud