summaryrefslogtreecommitdiffstats
path: root/src/postsuper/postsuper.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/postsuper/postsuper.c')
-rw-r--r--src/postsuper/postsuper.c1604
1 files changed, 1604 insertions, 0 deletions
diff --git a/src/postsuper/postsuper.c b/src/postsuper/postsuper.c
new file mode 100644
index 0000000..d3f2d5b
--- /dev/null
+++ b/src/postsuper/postsuper.c
@@ -0,0 +1,1604 @@
+/*++
+/* NAME
+/* postsuper 1
+/* SUMMARY
+/* Postfix superintendent
+/* SYNOPSIS
+/* .fi
+/* \fBpostsuper\fR [\fB-psSv\fR]
+/* [\fB-c \fIconfig_dir\fR] [\fB-d \fIqueue_id\fR]
+/* [\fB-e \fIqueue_id\fR] [\fB-f \fIqueue_id\fR]
+/* [\fB-h \fIqueue_id\fR] [\fB-H \fIqueue_id\fR]
+/* [\fB-r \fIqueue_id\fR] [\fIdirectory ...\fR]
+/* DESCRIPTION
+/* The \fBpostsuper\fR(1) command does maintenance jobs on the Postfix
+/* queue. Use of the command is restricted to the superuser.
+/* See the \fBpostqueue\fR(1) command for unprivileged queue operations
+/* such as listing or flushing the mail queue.
+/*
+/* By default, \fBpostsuper\fR(1) performs the operations
+/* requested with the
+/* \fB-s\fR and \fB-p\fR command-line options on all Postfix queue
+/* directories - this includes the \fBincoming\fR, \fBactive\fR,
+/* \fBdeferred\fR, and \fBhold\fR directories with message
+/* files and the \fBbounce\fR,
+/* \fBdefer\fR, \fBtrace\fR and \fBflush\fR directories with log files.
+/*
+/* Options:
+/* .IP "\fB-c \fIconfig_dir\fR"
+/* The \fBmain.cf\fR configuration file is in the named directory
+/* instead of the default configuration directory. See also the
+/* MAIL_CONFIG environment setting below.
+/* .IP "\fB-d \fIqueue_id\fR"
+/* Delete one message with the named queue ID from the named
+/* mail queue(s) (default: \fBhold\fR, \fBincoming\fR, \fBactive\fR and
+/* \fBdeferred\fR).
+/*
+/* To delete multiple files, specify the \fB-d\fR option multiple
+/* times, or specify a \fIqueue_id\fR of \fB-\fR to read queue IDs
+/* from standard input. For example, to delete all mail
+/* with exactly one recipient \fBuser@example.com\fR:
+/* .sp
+/* .nf
+/* postqueue -j | jq -r '
+/* # See JSON OBJECT FORMAT section in the postqueue(1) manpage
+/* select(.recipients[0].address == "user@example.com")
+/* | select(.recipients[1].address == null)
+/* | .queue_id
+/* ' | postsuper -d -
+/* .fi
+/* .sp
+/* (note the "jq -r" option), or the historical form:
+/* .sp
+/* .nf
+/* mailq | tail -n +2 | grep -v '^ *(' | awk 'BEGIN { RS = "" }
+/* # $7=sender, $8=recipient1, $9=recipient2
+/* { if ($8 == "user@example.com" && $9 == "")
+/* print $1 }
+/* ' | tr -d '*!' | postsuper -d -
+/* .fi
+/* .sp
+/* Specify "\fB-d ALL\fR" to remove all messages; for example, specify
+/* "\fB-d ALL deferred\fR" to delete all mail in the \fBdeferred\fR queue.
+/* As a safety measure, the word \fBALL\fR must be specified in upper
+/* case.
+/* .sp
+/* Warning: Postfix queue IDs are reused (always with Postfix
+/* <= 2.8; and with Postfix >= 2.9 when enable_long_queue_ids=no).
+/* There is a very small possibility that postsuper deletes the
+/* wrong message file when it is executed while the Postfix mail
+/* system is delivering mail.
+/* .sp
+/* The scenario is as follows:
+/* .RS
+/* .IP 1)
+/* The Postfix queue manager deletes the message that \fBpostsuper\fR(1)
+/* is asked to delete, because Postfix is finished with the
+/* message (it is delivered, or it is returned to the sender).
+/* .IP 2)
+/* New mail arrives, and the new message is given the same queue ID
+/* as the message that \fBpostsuper\fR(1) is supposed to delete.
+/* The probability for reusing a deleted queue ID is about 1 in 2**15
+/* (the number of different microsecond values that the system clock
+/* can distinguish within a second).
+/* .IP 3)
+/* \fBpostsuper\fR(1) deletes the new message, instead of the old
+/* message that it should have deleted.
+/* .RE
+/* .IP "\fB-e \fIqueue_id\fR"
+/* .IP "\fB-f \fIqueue_id\fR"
+/* Request forced expiration for one message with the named
+/* queue ID in the named mail queue(s) (default: \fBhold\fR,
+/* \fBincoming\fR, \fBactive\fR and \fBdeferred\fR).
+/* .RS
+/* .IP \(bu
+/* The message will be returned to the sender when the queue
+/* manager attempts to deliver that message (note that Postfix
+/* will never deliver messages in the \fBhold\fR queue).
+/* .IP \(bu
+/* The \fB-e\fR and \fB-f\fR options both request forced
+/* expiration. The difference is that \fB-f\fR will also release
+/* a message if it is in the \fBhold\fR queue. With \fB-e\fR, such
+/* a message would not be returned to the sender until it is
+/* released with \fB-f\fR or \fB-H\fR.
+/* .IP \(bu
+/* When a deferred message is force-expired, the return message
+/* will state the reason for the delay. Otherwise, the reason
+/* will be "message is administratively expired".
+/* .RE
+/* .IP
+/* To expire multiple files, specify the \fB-e\fR or \fB-f\fR
+/* option multiple times, or specify a \fIqueue_id\fR of \fB-\fR
+/* to read queue IDs from standard input (see the \fB-d\fR option
+/* above for an example, but be sure to replace \fB-d\fR in
+/* the example).
+/* .sp
+/* Specify "\fB-e ALL\fR" or "\fB-f ALL\fR" to expire all
+/* messages; for example, specify "\fB-e ALL deferred\fR" to
+/* expire all mail in the \fBdeferred\fR queue. As a safety
+/* measure, the word \fBALL\fR must be specified in upper case.
+/* .sp
+/* These features are available in Postfix 3.5 and later.
+/* .IP "\fB-h \fIqueue_id\fR"
+/* Put mail "on hold" so that no attempt is made to deliver it.
+/* Move one message with the named queue ID from the named
+/* mail queue(s) (default: \fBincoming\fR, \fBactive\fR and
+/* \fBdeferred\fR) to the \fBhold\fR queue.
+/*
+/* To hold multiple files, specify the \fB-h\fR option multiple
+/* times, or specify a \fIqueue_id\fR of \fB-\fR to read queue IDs
+/* from standard input.
+/* .sp
+/* Specify "\fB-h ALL\fR" to hold all messages; for example, specify
+/* "\fB-h ALL deferred\fR" to hold all mail in the \fBdeferred\fR queue.
+/* As a safety measure, the word \fBALL\fR must be specified in upper
+/* case.
+/* .sp
+/* Note: while mail is "on hold" it will not expire when its
+/* time in the queue exceeds the \fBmaximal_queue_lifetime\fR
+/* or \fBbounce_queue_lifetime\fR setting. It becomes subject to
+/* expiration after it is released from "hold".
+/* .sp
+/* This feature is available in Postfix 2.0 and later.
+/* .IP "\fB-H \fIqueue_id\fR"
+/* Release mail that was put "on hold".
+/* Move one message with the named queue ID from the named
+/* mail queue(s) (default: \fBhold\fR) to the \fBdeferred\fR queue.
+/*
+/* To release multiple files, specify the \fB-H\fR option multiple
+/* times, or specify a \fIqueue_id\fR of \fB-\fR to read queue IDs
+/* from standard input.
+/* .sp
+/* Note: specify "\fBpostsuper -r\fR" to release mail that was kept on
+/* hold for a significant fraction of \fB$maximal_queue_lifetime\fR
+/* or \fB$bounce_queue_lifetime\fR, or longer.
+/* .sp
+/* Specify "\fB-H ALL\fR" to release all mail that is "on hold".
+/* As a safety measure, the word \fBALL\fR must be specified in upper
+/* case.
+/* .sp
+/* This feature is available in Postfix 2.0 and later.
+/* .IP \fB-p\fR
+/* Purge old temporary files that are left over after system or
+/* software crashes.
+/* The \fB-p\fR, \fB-s\fR, and \fB-S\fR operations are done
+/* before other operations.
+/* .IP "\fB-r \fIqueue_id\fR"
+/* Requeue the message with the named queue ID from the named
+/* mail queue(s) (default: \fBhold\fR, \fBincoming\fR, \fBactive\fR and
+/* \fBdeferred\fR).
+/*
+/* To requeue multiple files, specify the \fB-r\fR option multiple
+/* times, or specify a \fIqueue_id\fR of \fB-\fR to read queue IDs
+/* from standard input.
+/* .sp
+/* Specify "\fB-r ALL\fR" to requeue all messages. As a safety
+/* measure, the word \fBALL\fR must be specified in upper case.
+/* .sp
+/* A requeued message is moved to the \fBmaildrop\fR queue,
+/* from where it is copied by the \fBpickup\fR(8) and
+/* \fBcleanup\fR(8) daemons to a new queue file. In many
+/* respects its handling differs from that of a new local
+/* submission.
+/* .RS
+/* .IP \(bu
+/* The message is not subjected to the smtpd_milters or
+/* non_smtpd_milters settings. When mail has passed through
+/* an external content filter, this would produce incorrect
+/* results with Milter applications that depend on original
+/* SMTP connection state information.
+/* .IP \(bu
+/* The message is subjected again to mail address rewriting
+/* and substitution. This is useful when rewriting rules or
+/* virtual mappings have changed.
+/* .sp
+/* The address rewriting context (local or remote) is the same
+/* as when the message was received.
+/* .IP \(bu
+/* The message is subjected to the same content_filter settings
+/* (if any) as used for new local mail submissions. This is
+/* useful when content_filter settings have changed.
+/* .RE
+/* .IP
+/* Warning: Postfix queue IDs are reused (always with Postfix
+/* <= 2.8; and with Postfix >= 2.9 when enable_long_queue_ids=no).
+/* There is a very small possibility that \fBpostsuper\fR(1) requeues
+/* the wrong message file when it is executed while the Postfix mail
+/* system is running, but no harm should be done.
+/* .sp
+/* This feature is available in Postfix 1.1 and later.
+/* .IP \fB-s\fR
+/* Structure check and structure repair. This should be done once
+/* before Postfix startup.
+/* The \fB-p\fR, \fB-s\fR, and \fB-S\fR operations are done
+/* before other operations.
+/* .RS
+/* .IP \(bu
+/* Rename files whose name does not match the message file inode
+/* number. This operation is necessary after restoring a mail
+/* queue from a different machine or from backup, when queue
+/* files were created with Postfix <= 2.8 or with
+/* "enable_long_queue_ids = no".
+/* .IP \(bu
+/* Move queue files that are in the wrong place in the file system
+/* hierarchy and remove subdirectories that are no longer needed.
+/* File position rearrangements are necessary after a change in the
+/* \fBhash_queue_names\fR and/or \fBhash_queue_depth\fR
+/* configuration parameters.
+/* .IP \(bu
+/* Rename queue files created with "enable_long_queue_ids =
+/* yes" to short names, for migration to Postfix <= 2.8. The
+/* procedure is as follows:
+/* .sp
+/* .nf
+/* .na
+/* # postfix stop
+/* # postconf enable_long_queue_ids=no
+/* # postsuper
+/* .ad
+/* .fi
+/* .sp
+/* Run \fBpostsuper\fR(1) repeatedly until it stops reporting
+/* file name changes.
+/* .RE
+/* .IP \fB-S\fR
+/* A redundant version of \fB-s\fR that requires that long
+/* file names also match the message file inode number. This
+/* option exists for testing purposes, and is available with
+/* Postfix 2.9 and later.
+/* The \fB-p\fR, \fB-s\fR, and \fB-S\fR operations are done
+/* before other operations.
+/* .IP \fB-v\fR
+/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR
+/* options make the software increasingly verbose.
+/* DIAGNOSTICS
+/* Problems are reported to the standard error stream and to
+/* \fBsyslogd\fR(8) or \fBpostlogd\fR(8).
+/*
+/* \fBpostsuper\fR(1) reports the number of messages deleted
+/* with \fB-d\fR, the number of messages expired with \fB-e\fR,
+/* the number of messages expired or released with \fB-f\fR,
+/* the number of messages held or released with \fB-h\fR or
+/* \fB-H\fR, the number of messages requeued with \fB-r\fR,
+/* and the number of messages whose queue file name was fixed
+/* with \fB-s\fR. The report is written to the standard error
+/* stream and to \fBsyslogd\fR(8) or \fBpostlogd\fR(8).
+/* ENVIRONMENT
+/* .ad
+/* .fi
+/* .IP MAIL_CONFIG
+/* Directory with the \fBmain.cf\fR file.
+/* BUGS
+/* Mail that is not sanitized by Postfix (i.e. mail in the \fBmaildrop\fR
+/* queue) cannot be placed "on hold".
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* The following \fBmain.cf\fR parameters are especially relevant to
+/* this program.
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBhash_queue_depth (1)\fR"
+/* The number of subdirectory levels for queue directories listed with
+/* the hash_queue_names parameter.
+/* .IP "\fBhash_queue_names (deferred, defer)\fR"
+/* The names of queue directories that are split across multiple
+/* subdirectory levels.
+/* .IP "\fBimport_environment (see 'postconf -d' output)\fR"
+/* The list of environment parameters that a privileged Postfix
+/* process will import from a non-Postfix parent process, or name=value
+/* environment overrides.
+/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
+/* The location of the Postfix top-level queue directory.
+/* .IP "\fBsyslog_facility (mail)\fR"
+/* The syslog facility of Postfix logging.
+/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
+/* A prefix that is prepended to the process name in syslog
+/* records, so that, for example, "smtpd" becomes "prefix/smtpd".
+/* .PP
+/* Available in Postfix version 2.9 and later:
+/* .IP "\fBenable_long_queue_ids (no)\fR"
+/* Enable long, non-repeating, queue IDs (queue file names).
+/* SEE ALSO
+/* sendmail(1), Sendmail-compatible user interface
+/* postqueue(1), unprivileged queue operations
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <stdio.h> /* remove() */
+#include <utime.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <msg.h>
+#include <vstream.h>
+#include <msg_vstream.h>
+#include <scan_dir.h>
+#include <vstring.h>
+#include <safe.h>
+#include <set_ugid.h>
+#include <argv.h>
+#include <vstring_vstream.h>
+#include <sane_fsops.h>
+#include <myrand.h>
+#include <warn_stat.h>
+#include <clean_env.h>
+#include <safe_open.h>
+#include <name_mask.h>
+
+/* Global library. */
+
+#include <mail_task.h>
+#include <mail_conf.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#define MAIL_QUEUE_INTERNAL
+#include <mail_queue.h>
+#include <mail_open_ok.h>
+#include <file_id.h>
+#include <mail_parm_split.h>
+#include <maillog_client.h>
+
+/* Application-specific. */
+
+#define MAX_TEMP_AGE (7 * 60 * 60 * 24) /* temp file maximal age */
+#define STR vstring_str /* silly little macro */
+
+#define ACTION_STRUCT (1<<0) /* fix file organization */
+#define ACTION_PURGE (1<<1) /* purge old temp files */
+#define ACTION_DELETE_ONE (1<<2) /* delete named queue file(s) */
+#define ACTION_DELETE_ALL (1<<3) /* delete all queue file(s) */
+#define ACTION_REQUEUE_ONE (1<<4) /* requeue named queue file(s) */
+#define ACTION_REQUEUE_ALL (1<<5) /* requeue all queue file(s) */
+#define ACTION_HOLD_ONE (1<<6) /* put named queue file(s) on hold */
+#define ACTION_HOLD_ALL (1<<7) /* put all messages on hold */
+#define ACTION_RELEASE_ONE (1<<8) /* release named queue file(s) */
+#define ACTION_RELEASE_ALL (1<<9) /* release all "on hold" mail */
+#define ACTION_STRUCT_RED (1<<10) /* fix long queue ID inode fields */
+#define ACTION_EXPIRE_ONE (1<<11) /* expire named queue file(s) */
+#define ACTION_EXPIRE_ALL (1<<12) /* expire all queue file(s) */
+#define ACTION_EXP_REL_ONE (1<<13) /* expire+release named queue file(s) */
+#define ACTION_EXP_REL_ALL (1<<14) /* expire+release all queue file(s) */
+
+#define ACTION_DEFAULT (ACTION_STRUCT | ACTION_PURGE)
+
+ /*
+ * Actions that operate on individually named queue files. These must never
+ * be done after fixing queue file names to match their inode number because
+ * the target file may have been replaced. Actions that move files are safe
+ * only when queue file names match their inode number, otherwise mail can
+ * be lost due to filename collisions.
+ */
+#define ACTIONS_BY_QUEUE_ID (ACTION_DELETE_ONE | ACTION_REQUEUE_ONE \
+ | ACTION_HOLD_ONE | ACTION_RELEASE_ONE \
+ | ACTION_EXPIRE_ONE | ACTION_EXP_REL_ONE)
+
+ /*
+ * Mass actions. Actions that move files are safe only when queue file names
+ * match their inode number, otherwise mail can be lost due to filename
+ * collisions.
+ */
+#define ACTIONS_BY_WILDCARD (ACTION_DELETE_ALL | ACTION_REQUEUE_ALL \
+ | ACTION_HOLD_ALL | ACTION_RELEASE_ALL \
+ | ACTION_EXPIRE_ALL | ACTION_EXP_REL_ALL)
+
+#define ACTIONS_FOR_REPAIR (ACTION_PURGE | ACTION_STRUCT \
+ | ACTION_STRUCT_RED)
+
+ /*
+ * Information about queue directories and what we expect to do there. If a
+ * file has unexpected owner permissions and is older than some threshold,
+ * the file is discarded. We don't step into maildrop subdirectories - if
+ * maildrop is writable, we might end up in the wrong place, deleting the
+ * wrong information.
+ */
+struct queue_info {
+ char *name; /* directory name */
+ int perms; /* expected permissions */
+ int flags; /* see below */
+};
+
+#define RECURSE (1<<0) /* step into subdirectories */
+#define DONT_RECURSE 0 /* don't step into directories */
+
+static struct queue_info queue_info[] = {
+ MAIL_QUEUE_MAILDROP, MAIL_QUEUE_STAT_READY, DONT_RECURSE,
+ MAIL_QUEUE_INCOMING, MAIL_QUEUE_STAT_READY, RECURSE,
+ MAIL_QUEUE_ACTIVE, MAIL_QUEUE_STAT_READY, RECURSE,
+ MAIL_QUEUE_DEFERRED, MAIL_QUEUE_STAT_READY, RECURSE,
+ MAIL_QUEUE_HOLD, MAIL_QUEUE_STAT_READY, RECURSE,
+ MAIL_QUEUE_TRACE, 0600, RECURSE,
+ MAIL_QUEUE_DEFER, 0600, RECURSE,
+ MAIL_QUEUE_BOUNCE, 0600, RECURSE,
+ MAIL_QUEUE_FLUSH, 0600, RECURSE,
+ 0,
+};
+
+ /*
+ * Directories with per-message meta files.
+ */
+const char *log_queue_names[] = {
+ MAIL_QUEUE_BOUNCE,
+ MAIL_QUEUE_DEFER,
+ MAIL_QUEUE_TRACE,
+ 0,
+};
+
+ /*
+ * Cruft that we append to a file name when a queue ID is named after the
+ * message file inode number. This cruft must not pass mail_queue_id_ok() so
+ * that the queue manager will ignore it, should people be so unwise as to
+ * run this operation on a live mail system.
+ */
+#define SUFFIX "#FIX"
+#define SUFFIX_LEN 4
+
+ /*
+ * Grr. These counters are global, because C only has clumsy ways to return
+ * multiple results from a function.
+ */
+static int message_requeued = 0; /* requeued messages */
+static int message_held = 0; /* messages put on hold */
+static int message_released = 0; /* messages released from hold */
+static int message_deleted = 0; /* deleted messages */
+static int message_expired = 0; /* expired messages */
+static int inode_fixed = 0; /* queue id matched to inode number */
+static int inode_mismatch = 0; /* queue id inode mismatch */
+static int position_mismatch = 0; /* file position mismatch */
+
+ /*
+ * Silly little macros. These translate arcane expressions into something
+ * more at a conceptual level.
+ */
+#define MESSAGE_QUEUE(qp) ((qp)->perms == MAIL_QUEUE_STAT_READY)
+#define READY_MESSAGE(st) (((st).st_mode & S_IRWXU) == MAIL_QUEUE_STAT_READY)
+
+/* find_queue_info - look up expected permissions field by queue name */
+
+static struct queue_info *find_queue_info(const char *queue_name)
+{
+ struct queue_info *qp;
+
+ for (qp = queue_info; qp->name; qp++)
+ if (strcmp(queue_name, qp->name) == 0)
+ return (qp);
+ msg_fatal("invalid directory name: %s", queue_name);
+}
+
+/* postremove - remove file with extreme prejudice */
+
+static int postremove(const char *path)
+{
+ int ret;
+
+ if ((ret = remove(path)) < 0) {
+ if (errno != ENOENT)
+ msg_fatal("remove file %s: %m", path);
+ } else {
+ if (msg_verbose)
+ msg_info("removed file %s", path);
+ }
+ return (ret);
+}
+
+/* postexpire - expire file, setting the group execute permission */
+
+static int postexpire(const char *path)
+{
+ static VSTRING *why = 0;
+ VSTREAM *fp;
+ struct stat st;
+
+ /*
+ * Initialize.
+ */
+ if (why == 0)
+ why = vstring_alloc(100);
+
+ /*
+ * We don't actually verify the file content, therefore safe_open() the
+ * queue file so that we won't add group execute permission to some file
+ * outside of the mail queue.
+ */
+ if ((fp = safe_open(path, O_RDWR, 0, &st, -1, -1, why)) == 0) {
+ if (errno != ENOENT)
+ msg_warn("expire file %s: %s", path, vstring_str(why));
+ return (-1);
+ }
+#define POSTEXPIRE_RETURN(x) do { \
+ (void) vstream_fclose(fp); \
+ return (x); \
+ } while (0)
+
+ if (!READY_MESSAGE(st))
+ POSTEXPIRE_RETURN(-1); /* must not expire */
+ if ((st.st_mode & MAIL_QUEUE_STAT_EXPIRE) != 0)
+ POSTEXPIRE_RETURN(-1); /* already expired */
+ if (fchmod(vstream_fileno(fp),
+ (st.st_mode | MAIL_QUEUE_STAT_EXPIRE) & ~S_IFMT) < 0) {
+ msg_warn("expire file %s: cannot set permission: %m", path);
+ POSTEXPIRE_RETURN(-1);
+ }
+ POSTEXPIRE_RETURN(0);
+}
+
+/* postrename - rename file with extreme prejudice */
+
+static int postrename(const char *old, const char *new)
+{
+ int ret;
+
+ if ((ret = sane_rename(old, new)) < 0) {
+ if (errno != ENOENT
+ || mail_queue_mkdirs(new) < 0
+ || (ret = sane_rename(old, new)) < 0)
+ if (errno != ENOENT)
+ msg_fatal("rename file %s as %s: %m", old, new);
+ } else {
+ if (msg_verbose)
+ msg_info("renamed file %s as %s", old, new);
+ }
+ return (ret);
+}
+
+/* postrmdir - remove directory with extreme prejudice */
+
+static int postrmdir(const char *path)
+{
+ int ret;
+
+ if ((ret = rmdir(path)) < 0) {
+ if (errno != ENOENT)
+ msg_fatal("remove directory %s: %m", path);
+ } else {
+ if (msg_verbose)
+ msg_info("remove directory %s", path);
+ }
+ return (ret);
+}
+
+/* delete_one - delete one message instance and all its associated files */
+
+static void delete_one(const char **queue_names, const char *queue_id)
+{
+ struct stat st;
+ const char **msg_qpp;
+ const char **log_qpp;
+ const char *msg_path;
+ VSTRING *log_path_buf;
+ int found;
+ int tries;
+
+ /*
+ * Sanity check. No early returns beyond this point.
+ */
+ if (!mail_queue_id_ok(queue_id)) {
+ msg_warn("invalid mail queue id: %s", queue_id);
+ return;
+ }
+ log_path_buf = vstring_alloc(100);
+
+ /*
+ * Skip meta file directories. Delete trace/defer/bounce logfiles before
+ * deleting the corresponding message file, and only if the message file
+ * exists. This minimizes but does not eliminate a race condition with
+ * queue ID reuse which results in deleting the wrong files.
+ */
+ for (found = 0, tries = 0; found == 0 && tries < 2; tries++) {
+ for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) {
+ if (!MESSAGE_QUEUE(find_queue_info(*msg_qpp)))
+ continue;
+ if (mail_open_ok(*msg_qpp, queue_id, &st, &msg_path) != MAIL_OPEN_YES)
+ continue;
+ for (log_qpp = log_queue_names; *log_qpp != 0; log_qpp++)
+ postremove(mail_queue_path(log_path_buf, *log_qpp, queue_id));
+ if (postremove(msg_path) == 0) {
+ found = 1;
+ msg_info("%s: removed", queue_id);
+ break;
+ } /* else: maybe lost a race */
+ }
+ }
+ vstring_free(log_path_buf);
+ message_deleted += found;
+}
+
+/* requeue_one - requeue one message instance and delete its logfiles */
+
+static void requeue_one(const char **queue_names, const char *queue_id)
+{
+ struct stat st;
+ const char **msg_qpp;
+ const char *old_path;
+ VSTRING *new_path_buf;
+ int found;
+ int tries;
+ struct utimbuf tbuf;
+
+ /*
+ * Sanity check. No early returns beyond this point.
+ */
+ if (!mail_queue_id_ok(queue_id)) {
+ msg_warn("invalid mail queue id: %s", queue_id);
+ return;
+ }
+ new_path_buf = vstring_alloc(100);
+
+ /*
+ * Skip meta file directories. Like the mass requeue operation, we not
+ * delete defer or bounce logfiles, to avoid losing a race where the
+ * queue manager decides to bounce mail after all recipients have been
+ * tried.
+ */
+ for (found = 0, tries = 0; found == 0 && tries < 2; tries++) {
+ for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) {
+ if (strcmp(*msg_qpp, MAIL_QUEUE_MAILDROP) == 0)
+ continue;
+ if (!MESSAGE_QUEUE(find_queue_info(*msg_qpp)))
+ continue;
+ if (mail_open_ok(*msg_qpp, queue_id, &st, &old_path) != MAIL_OPEN_YES)
+ continue;
+ (void) mail_queue_path(new_path_buf, MAIL_QUEUE_MAILDROP, queue_id);
+ if (postrename(old_path, STR(new_path_buf)) == 0) {
+ tbuf.actime = tbuf.modtime = time((time_t *) 0);
+ if (utime(STR(new_path_buf), &tbuf) < 0)
+ msg_warn("%s: reset time stamps: %m", STR(new_path_buf));
+ msg_info("%s: requeued", queue_id);
+ found = 1;
+ break;
+ } /* else: maybe lost a race */
+ }
+ }
+ vstring_free(new_path_buf);
+ message_requeued += found;
+}
+
+/* hold_one - put "on hold" one message instance */
+
+static void hold_one(const char **queue_names, const char *queue_id)
+{
+ struct stat st;
+ const char **msg_qpp;
+ const char *old_path;
+ VSTRING *new_path_buf;
+ int found;
+ int tries;
+
+ /*
+ * Sanity check. No early returns beyond this point.
+ */
+ if (!mail_queue_id_ok(queue_id)) {
+ msg_warn("invalid mail queue id: %s", queue_id);
+ return;
+ }
+ new_path_buf = vstring_alloc(100);
+
+ /*
+ * Skip meta file directories. Like the mass requeue operation, we not
+ * delete defer or bounce logfiles, to avoid losing a race where the
+ * queue manager decides to bounce mail after all recipients have been
+ * tried.
+ *
+ * XXX We must not put maildrop mail on hold because that would mix already
+ * sanitized mail with mail that still needs to be sanitized.
+ */
+ for (found = 0, tries = 0; found == 0 && tries < 2; tries++) {
+ for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) {
+ if (strcmp(*msg_qpp, MAIL_QUEUE_MAILDROP) == 0)
+ continue;
+ if (strcmp(*msg_qpp, MAIL_QUEUE_HOLD) == 0)
+ continue;
+ if (!MESSAGE_QUEUE(find_queue_info(*msg_qpp)))
+ continue;
+ if (mail_open_ok(*msg_qpp, queue_id, &st, &old_path) != MAIL_OPEN_YES)
+ continue;
+ (void) mail_queue_path(new_path_buf, MAIL_QUEUE_HOLD, queue_id);
+ if (postrename(old_path, STR(new_path_buf)) == 0) {
+ msg_info("%s: placed on hold", queue_id);
+ found = 1;
+ break;
+ } /* else: maybe lost a race */
+ }
+ }
+ vstring_free(new_path_buf);
+ message_held += found;
+}
+
+/* release_one - release one message instance that was placed "on hold" */
+
+static void release_one(const char **queue_names, const char *queue_id)
+{
+ struct stat st;
+ const char **msg_qpp;
+ const char *old_path;
+ VSTRING *new_path_buf;
+ int found;
+
+ /*
+ * Sanity check. No early returns beyond this point.
+ */
+ if (!mail_queue_id_ok(queue_id)) {
+ msg_warn("invalid mail queue id: %s", queue_id);
+ return;
+ }
+ new_path_buf = vstring_alloc(100);
+
+ /*
+ * Skip inapplicable directories. This can happen when -H is combined
+ * with other operations.
+ */
+ found = 0;
+ for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) {
+ if (strcmp(*msg_qpp, MAIL_QUEUE_HOLD) != 0)
+ continue;
+ if (mail_open_ok(*msg_qpp, queue_id, &st, &old_path) != MAIL_OPEN_YES)
+ continue;
+ (void) mail_queue_path(new_path_buf, MAIL_QUEUE_DEFERRED, queue_id);
+ if (postrename(old_path, STR(new_path_buf)) == 0) {
+ msg_info("%s: released from hold", queue_id);
+ found = 1;
+ break;
+ }
+ }
+ vstring_free(new_path_buf);
+ message_released += found;
+}
+
+/* expire_one - expire one message instance */
+
+static void expire_one(const char **queue_names, const char *queue_id)
+{
+ struct stat st;
+ const char **msg_qpp;
+ const char *msg_path;
+ int found;
+ int tries;
+
+ /*
+ * Sanity check. No early returns beyond this point.
+ */
+ if (!mail_queue_id_ok(queue_id)) {
+ msg_warn("invalid mail queue id: %s", queue_id);
+ return;
+ }
+
+ /*
+ * Skip meta file directories.
+ */
+ for (found = 0, tries = 0; found == 0 && tries < 2; tries++) {
+ for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) {
+ if (!MESSAGE_QUEUE(find_queue_info(*msg_qpp)))
+ continue;
+ if (strcmp(*msg_qpp, MAIL_QUEUE_MAILDROP) == 0)
+ continue;
+ if (mail_open_ok(*msg_qpp, queue_id, &st, &msg_path) != MAIL_OPEN_YES)
+ continue;
+ if (postexpire(msg_path) == 0) {
+ found = 1;
+ msg_info("%s: expired", queue_id);
+ break;
+ } /* else: maybe lost a race */
+ }
+ }
+ message_expired += found;
+}
+
+/* exp_rel_one - expire or release one message instance */
+
+static void exp_rel_one(const char **queue_names, const char *queue_id)
+{
+ expire_one(queue_names, queue_id);
+ release_one(queue_names, queue_id);
+}
+
+/* operate_stream - operate on queue IDs given on stream */
+
+static void operate_stream(VSTREAM *fp,
+ void (*operator) (const char **, const char *),
+ const char **queues)
+{
+ VSTRING *buf = vstring_alloc(20);
+
+ while (vstring_get_nonl(buf, fp) != VSTREAM_EOF)
+ operator(queues, STR(buf));
+
+ vstring_free(buf);
+}
+
+/* fix_queue_id - make message queue ID match inode number */
+
+static int fix_queue_id(const char *actual_path, const char *actual_queue,
+ const char *actual_id, struct stat * st)
+{
+ VSTRING *old_path = vstring_alloc(10);
+ VSTRING *new_path = vstring_alloc(10);
+ VSTRING *new_id = vstring_alloc(10);
+ const char **log_qpp;
+ char *cp;
+ int ret;
+
+ /*
+ * Create the new queue ID from the existing time digits and from the new
+ * inode number. Since we are renaming multiple files, the new name must
+ * be deterministic so that we can recover even when the renaming
+ * operation is interrupted in the middle.
+ */
+ if (MQID_FIND_LG_INUM_SEPARATOR(cp, actual_id) == 0) {
+ /* Short->short queue ID. Replace the inode portion. */
+ vstring_sprintf(new_id, "%.*s%s",
+ MQID_SH_USEC_PAD, actual_id,
+ get_file_id_st(st, 0));
+ } else if (var_long_queue_ids) {
+ /* Long->long queue ID. Replace the inode portion. */
+ vstring_sprintf(new_id, "%.*s%c%s",
+ (int) (cp - actual_id), actual_id, MQID_LG_INUM_SEP,
+ get_file_id_st(st, 1));
+ } else {
+ /* Long->short queue ID. Reformat time and replace inode portion. */
+ MQID_LG_GET_HEX_USEC(new_id, cp);
+ vstring_strcat(new_id, get_file_id_st(st, 0));
+ }
+
+ /*
+ * Rename logfiles before renaming the message file, so that we can
+ * recover when a previous attempt was interrupted.
+ */
+ for (log_qpp = log_queue_names; *log_qpp; log_qpp++) {
+ mail_queue_path(old_path, *log_qpp, actual_id);
+ mail_queue_path(new_path, *log_qpp, STR(new_id));
+ vstring_strcat(new_path, SUFFIX);
+ postrename(STR(old_path), STR(new_path));
+ }
+
+ /*
+ * Rename the message file last, so that we know that we are done with
+ * this message and with all its logfiles.
+ */
+ mail_queue_path(new_path, actual_queue, STR(new_id));
+ vstring_strcat(new_path, SUFFIX);
+ ret = postrename(actual_path, STR(new_path));
+
+ /*
+ * Clean up.
+ */
+ vstring_free(old_path);
+ vstring_free(new_path);
+ vstring_free(new_id);
+
+ return (ret);
+}
+
+/* super - check queue structure, clean up, do wild-card operations */
+
+static void super(const char **queues, int action)
+{
+ ARGV *hash_queue_names = argv_split(var_hash_queue_names, CHARS_COMMA_SP);
+ VSTRING *actual_path = vstring_alloc(10);
+ VSTRING *wanted_path = vstring_alloc(10);
+ struct stat st;
+ const char *queue_name;
+ SCAN_DIR *info;
+ char *path;
+ int actual_depth;
+ int wanted_depth;
+ char **cpp;
+ struct queue_info *qp;
+ unsigned long inum;
+ int long_name;
+ int error;
+
+ /*
+ * This routine was originally written to do multiple mass operations in
+ * one pass. However this hard-coded the order of operations which became
+ * difficult to explain. As of Postfix 3.5 this routine is called for one
+ * mass operation at a time, in the user-specified order. The exception
+ * is that repair operations (purging stale files, queue hashing, and
+ * file-inode match) are combined and done before other mass operations.
+ */
+
+ /*
+ * Make sure every file is in the right place, clean out stale files, and
+ * remove non-file/non-directory objects.
+ */
+ while ((queue_name = *queues++) != 0) {
+
+ if (msg_verbose)
+ msg_info("queue: %s", queue_name);
+
+ /*
+ * Look up queue-specific properties: desired hashing depth, what
+ * file permissions to look for, and whether or not it is desirable
+ * to step into subdirectories.
+ */
+ qp = find_queue_info(queue_name);
+ for (cpp = hash_queue_names->argv; /* void */ ; cpp++) {
+ if (*cpp == 0) {
+ wanted_depth = 0;
+ break;
+ }
+ if (strcmp(*cpp, queue_name) == 0) {
+ wanted_depth = var_hash_queue_depth;
+ break;
+ }
+ }
+
+ /*
+ * Sanity check. Some queues just cannot be recursive.
+ */
+ if (wanted_depth > 0 && (qp->flags & RECURSE) == 0)
+ msg_fatal("%s queue must not be hashed", queue_name);
+
+ /*
+ * Other per-directory initialization.
+ */
+ info = scan_dir_open(queue_name);
+ actual_depth = 0;
+
+ for (;;) {
+
+ /*
+ * If we reach the end of a subdirectory, return to its parent.
+ * Delete subdirectories that are no longer needed.
+ */
+ if ((path = scan_dir_next(info)) == 0) {
+ if (actual_depth == 0)
+ break;
+ if (actual_depth > wanted_depth)
+ postrmdir(scan_dir_path(info));
+ scan_dir_pop(info);
+ actual_depth--;
+ continue;
+ }
+
+ /*
+ * If we stumble upon a subdirectory, enter it, if it is
+ * considered safe to do so. Otherwise, try to remove the
+ * subdirectory at a later stage.
+ */
+ if (strlen(path) == 1 && (qp->flags & RECURSE) != 0) {
+ actual_depth++;
+ scan_dir_push(info, path);
+ continue;
+ }
+
+ /*
+ * From here on we need to keep track of operations that
+ * invalidate or revalidate the actual_path and path variables,
+ * otherwise we can hit the wrong files.
+ */
+ vstring_sprintf(actual_path, "%s/%s", scan_dir_path(info), path);
+ if (lstat(STR(actual_path), &st) < 0)
+ continue;
+
+ /*
+ * Remove alien directories. If maildrop is compromised, then we
+ * cannot abort just because we cannot remove someone's
+ * directory.
+ */
+ if (S_ISDIR(st.st_mode)) {
+ if (rmdir(STR(actual_path)) < 0) {
+ if (errno != ENOENT)
+ msg_warn("remove subdirectory %s: %m", STR(actual_path));
+ } else {
+ if (msg_verbose)
+ msg_info("remove subdirectory %s", STR(actual_path));
+ }
+ /* No further work on this object is possible. */
+ continue;
+ }
+
+ /*
+ * Mass deletion. We count the deletion of mail that this system
+ * has taken responsibility for. XXX This option does not use
+ * mail_queue_remove(), so that it can avoid having to first move
+ * queue files to the "right" subdirectory level.
+ */
+ if (action & ACTION_DELETE_ALL) {
+ if (postremove(STR(actual_path)) == 0)
+ if (MESSAGE_QUEUE(qp) && READY_MESSAGE(st))
+ message_deleted++;
+ /* No further work on this object is possible. */
+ continue;
+ }
+
+ /*
+ * Remove non-file objects and old temporary files. Be careful
+ * not to delete bounce or defer logs just because they are more
+ * than a couple days old.
+ */
+ if (!S_ISREG(st.st_mode)
+ || ((action & ACTION_PURGE) != 0
+ && MESSAGE_QUEUE(qp)
+ && !READY_MESSAGE(st)
+ && time((time_t *) 0) > st.st_mtime + MAX_TEMP_AGE)) {
+ (void) postremove(STR(actual_path));
+ /* No further work on this object is possible. */
+ continue;
+ }
+
+ /*
+ * Fix queueid#FIX names that were left from a previous pass over
+ * the queue where message queue file names were matched to their
+ * inode number. We strip the suffix and move the file into the
+ * proper subdirectory level. Make sure that the name minus
+ * suffix is well formed and that the name matches the file inode
+ * number.
+ */
+ if ((action & ACTION_STRUCT)
+ && strcmp(path + (strlen(path) - SUFFIX_LEN), SUFFIX) == 0) {
+ path[strlen(path) - SUFFIX_LEN] = 0; /* XXX */
+ if (!mail_queue_id_ok(path)) {
+ msg_warn("bogus file name: %s", STR(actual_path));
+ continue;
+ }
+ if (MESSAGE_QUEUE(qp)) {
+ MQID_GET_INUM(path, inum, long_name, error);
+ if (error) {
+ msg_warn("bogus file name: %s", STR(actual_path));
+ continue;
+ }
+ if (inum != (unsigned long) st.st_ino) {
+ msg_warn("name/inode mismatch: %s", STR(actual_path));
+ continue;
+ }
+ }
+ (void) mail_queue_path(wanted_path, queue_name, path);
+ if (postrename(STR(actual_path), STR(wanted_path)) < 0) {
+ /* No further work on this object is possible. */
+ continue;
+ } else {
+ if (MESSAGE_QUEUE(qp))
+ inode_fixed++;
+ vstring_strcpy(actual_path, STR(wanted_path));
+ /* At this point, path and actual_path are revalidated. */
+ }
+ }
+
+ /*
+ * Skip over files with illegal names. The library routines
+ * refuse to operate on them.
+ */
+ if (!mail_queue_id_ok(path)) {
+ msg_warn("bogus file name: %s", STR(actual_path));
+ continue;
+ }
+
+ /*
+ * See if the file name matches the file inode number. Skip meta
+ * file directories. This option requires that meta files be put
+ * into their proper place before queue files, so that we can
+ * rename queue files and meta files at the same time. Mis-named
+ * files are renamed to newqueueid#FIX on the first pass, and
+ * upon the second pass the #FIX is stripped off the name. Of
+ * course we have to be prepared that the program is interrupted
+ * before it completes, so any left-over newqueueid#FIX files
+ * have to be handled properly. XXX This option cannot use
+ * mail_queue_rename(), because the queue file name violates
+ * normal queue file syntax.
+ *
+ * By design there is no need to "fix" non-repeating names. What
+ * follows is applicable only when reverting from long names to
+ * short names, or when migrating short names from one queue to
+ * another.
+ */
+ if ((action & ACTION_STRUCT) != 0 && MESSAGE_QUEUE(qp)) {
+ MQID_GET_INUM(path, inum, long_name, error);
+ if (error) {
+ msg_warn("bogus file name: %s", STR(actual_path));
+ continue;
+ }
+ if ((long_name != 0 && var_long_queue_ids == 0)
+ || (inum != (unsigned long) st.st_ino
+ && (long_name == 0 || (action & ACTION_STRUCT_RED)))) {
+ inode_mismatch++; /* before we fix */
+ action &= ~ACTIONS_BY_WILDCARD;
+ fix_queue_id(STR(actual_path), queue_name, path, &st);
+ /* At this point, path and actual_path are invalidated. */
+ continue;
+ }
+ }
+
+ /*
+ * Mass requeuing. The pickup daemon will copy requeued mail to a
+ * new queue file, so that address rewriting is applied again.
+ * XXX This option does not use mail_queue_rename(), so that it
+ * can avoid having to first move queue files to the "right"
+ * subdirectory level. Like the requeue_one() routine, this code
+ * does not touch logfiles.
+ */
+ if ((action & ACTION_REQUEUE_ALL)
+ && MESSAGE_QUEUE(qp)
+ && strcmp(queue_name, MAIL_QUEUE_MAILDROP) != 0) {
+ (void) mail_queue_path(wanted_path, MAIL_QUEUE_MAILDROP, path);
+ if (postrename(STR(actual_path), STR(wanted_path)) == 0)
+ message_requeued++;
+ /* At this point, path and actual_path are invalidated. */
+ continue;
+ }
+
+ /*
+ * Many of the following actions may move queue files. To avoid
+ * loss of email due to file name collisions. we should do such
+ * actions only when the queue file names are known to match
+ * their inode number. Even with non-repeating queue IDs a name
+ * collision may happen when different queues are merged.
+ */
+
+ /*
+ * Mass expiration. We count the expiration of mail that this
+ * system has taken responsibility for.
+ */
+ if ((action & (ACTION_EXPIRE_ALL | ACTION_EXP_REL_ALL))
+ && MESSAGE_QUEUE(qp) && READY_MESSAGE(st)
+ && strcmp(queue_name, MAIL_QUEUE_MAILDROP) != 0
+ && postexpire(STR(actual_path)) == 0)
+ message_expired++;
+
+ /*
+ * Mass renaming to the "on hold" queue. XXX This option does not
+ * use mail_queue_rename(), so that it can avoid having to first
+ * move queue files to the "right" subdirectory level. Like the
+ * hold_one() routine, this code does not touch logfiles, and
+ * must not touch files in the maildrop queue, because maildrop
+ * files contain data that has not yet been sanitized and
+ * therefore must not be mixed with already sanitized mail.
+ */
+ if ((action & ACTION_HOLD_ALL)
+ && MESSAGE_QUEUE(qp)
+ && strcmp(queue_name, MAIL_QUEUE_MAILDROP) != 0
+ && strcmp(queue_name, MAIL_QUEUE_HOLD) != 0) {
+ (void) mail_queue_path(wanted_path, MAIL_QUEUE_HOLD, path);
+ if (postrename(STR(actual_path), STR(wanted_path)) == 0)
+ message_held++;
+ /* At this point, path and actual_path are invalidated. */
+ continue;
+ }
+
+ /*
+ * Mass release from the "on hold" queue. XXX This option does
+ * not use mail_queue_rename(), so that it can avoid having to
+ * first move queue files to the "right" subdirectory level. Like
+ * the release_one() routine, this code must not touch logfiles.
+ */
+ if ((action & (ACTION_RELEASE_ALL | ACTION_EXP_REL_ALL))
+ && strcmp(queue_name, MAIL_QUEUE_HOLD) == 0) {
+ (void) mail_queue_path(wanted_path, MAIL_QUEUE_DEFERRED, path);
+ if (postrename(STR(actual_path), STR(wanted_path)) == 0)
+ message_released++;
+ /* At this point, path and actual_path are invalidated. */
+ continue;
+ }
+
+ /*
+ * See if this file sits in the right place in the file system
+ * hierarchy. Its place may be wrong after a change to the
+ * hash_queue_{names,depth} parameter settings. This requires
+ * that the bounce/defer logfiles be at the right subdirectory
+ * level first, otherwise we would fail to properly rename
+ * bounce/defer logfiles.
+ */
+ if (action & ACTION_STRUCT) {
+ (void) mail_queue_path(wanted_path, queue_name, path);
+ if (strcmp(STR(actual_path), STR(wanted_path)) != 0) {
+ position_mismatch++; /* before we fix */
+ (void) postrename(STR(actual_path), STR(wanted_path));
+ /* At this point, path and actual_path are invalidated. */
+ continue;
+ }
+ }
+ }
+ scan_dir_close(info);
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(wanted_path);
+ vstring_free(actual_path);
+ argv_free(hash_queue_names);
+}
+
+/* interrupted - signal handler */
+
+static void interrupted(int sig)
+{
+
+ /*
+ * This commands requires root privileges. We therefore do not worry
+ * about hostile signals, and report problems via msg_warn().
+ *
+ * We use the in-kernel SIGINT handler address as an atomic variable to
+ * prevent nested interrupted() calls. For this reason, main() must
+ * configure interrupted() as SIGINT handler before other signal handlers
+ * are allowed to invoke interrupted(). See also similar code in
+ * postdrop.
+ */
+ if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
+ (void) signal(SIGQUIT, SIG_IGN);
+ (void) signal(SIGTERM, SIG_IGN);
+ (void) signal(SIGHUP, SIG_IGN);
+ if (inode_mismatch > 0 || inode_fixed > 0 || position_mismatch > 0)
+ msg_warn("OPERATION INCOMPLETE -- RERUN COMMAND TO FIX THE QUEUE FIRST");
+ if (sig)
+ _exit(sig);
+ }
+}
+
+/* fatal_warning - print warning if queue fix is incomplete */
+
+static void fatal_warning(void)
+{
+ interrupted(0);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+int main(int argc, char **argv)
+{
+ int fd;
+ struct stat st;
+ char *slash;
+ int action = 0;
+ const char **queues;
+ int c;
+ ARGV *import_env;
+ int saved_optind;
+
+ /*
+ * Defaults. The structural checks must fix the directory levels of "log
+ * file" directories (bounce, defer) before doing structural checks on
+ * the "message file" directories, so that we can find the logfiles in
+ * the right place when message files need to be renamed to match their
+ * inode number.
+ */
+ static char *default_queues[] = {
+ MAIL_QUEUE_DEFER, /* before message directories */
+ MAIL_QUEUE_BOUNCE, /* before message directories */
+ MAIL_QUEUE_MAILDROP,
+ MAIL_QUEUE_INCOMING,
+ MAIL_QUEUE_ACTIVE,
+ MAIL_QUEUE_DEFERRED,
+ MAIL_QUEUE_HOLD,
+ MAIL_QUEUE_FLUSH,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Be consistent with file permissions.
+ */
+ umask(022);
+
+ /*
+ * To minimize confusion, make sure that the standard file descriptors
+ * are open before opening anything else. XXX Work around for 44BSD where
+ * fstat can return EBADF on an open file descriptor.
+ */
+ for (fd = 0; fd < 3; fd++)
+ if (fstat(fd, &st) == -1
+ && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
+ msg_fatal("open /dev/null: %m");
+
+ /*
+ * Process this environment option as early as we can, to aid debugging.
+ */
+ if (safe_getenv(CONF_ENV_VERB))
+ msg_verbose = 1;
+
+ /*
+ * Initialize logging.
+ */
+ if ((slash = strrchr(argv[0], '/')) != 0 && slash[1])
+ argv[0] = slash + 1;
+ msg_vstream_init(argv[0], VSTREAM_ERR);
+ maillog_client_init(mail_task(argv[0]),
+ MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK);
+ set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0]));
+
+ /*
+ * Check the Postfix library version as soon as we enable logging.
+ */
+ MAIL_VERSION_CHECK;
+
+ /*
+ * Disallow unsafe practices, and refuse to run set-uid (or as the child
+ * of a set-uid process). Whenever a privileged wrapper program is
+ * needed, it must properly sanitize the real/effective/saved UID/GID,
+ * the secondary groups, the process environment, and so on. Otherwise,
+ * accidents can happen. If not with Postfix, then with other software.
+ */
+ if (unsafe() != 0)
+ msg_fatal("this postfix command must not run as a set-uid process");
+ if (getuid())
+ msg_fatal("use of this command is reserved for the superuser");
+
+ /*
+ * Parse JCL.
+ *
+ * First, find out what kind of actions are requested, without executing
+ * them. Later, we execute actions in mostly user-specified order.
+ */
+#define GETOPT_LIST "c:d:e:f:h:H:pr:sSv"
+
+ saved_optind = optind;
+ while ((c = GETOPT(argc, argv, GETOPT_LIST)) > 0) {
+ switch (c) {
+ default:
+ msg_fatal("usage: %s "
+ "[-c config_dir] "
+ "[-d queue_id (delete)] "
+ "[-e queue_id (expire)] "
+ "[-f queue_id (expire and/or un-hold)] "
+ "[-h queue_id (hold)] [-H queue_id (un-hold)] "
+ "[-p (purge temporary files)] [-r queue_id (requeue)] "
+ "[-s (structure fix)] [-S (redundant structure fix)]"
+ "[-v (verbose)] [queue...]", argv[0]);
+ case 'c':
+ if (*optarg != '/')
+ msg_fatal("-c requires absolute pathname");
+ if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
+ msg_fatal("setenv: %m");
+ break;
+ case 'd':
+ action |= (strcmp(optarg, "ALL") == 0 ?
+ ACTION_DELETE_ALL : ACTION_DELETE_ONE);
+ break;
+ case 'e':
+ action |= (strcmp(optarg, "ALL") == 0 ?
+ ACTION_EXPIRE_ALL : ACTION_EXPIRE_ONE);
+ break;
+ case 'f':
+ action |= (strcmp(optarg, "ALL") == 0 ?
+ ACTION_EXP_REL_ALL : ACTION_EXP_REL_ONE);
+ break;
+ case 'h':
+ action |= (strcmp(optarg, "ALL") == 0 ?
+ ACTION_HOLD_ALL : ACTION_HOLD_ONE);
+ break;
+ case 'H':
+ action |= (strcmp(optarg, "ALL") == 0 ?
+ ACTION_RELEASE_ALL : ACTION_RELEASE_ONE);
+ break;
+ case 'p':
+ action |= ACTION_PURGE;
+ break;
+ case 'r':
+ action |= (strcmp(optarg, "ALL") == 0 ?
+ ACTION_REQUEUE_ALL : ACTION_REQUEUE_ONE);
+ break;
+ case 'S':
+ action |= ACTION_STRUCT_RED;
+ /* FALLTHROUGH */
+ case 's':
+ action |= ACTION_STRUCT;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ }
+ }
+
+ /*
+ * Read the global configuration file and extract configuration
+ * information. The -c command option can override the default
+ * configuration directory location.
+ */
+ mail_conf_read();
+ /* Enforce consistent operation of different Postfix parts. */
+ import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ);
+ update_env(import_env->argv);
+ argv_free(import_env);
+ /* Re-evaluate mail_task() after reading main.cf. */
+ maillog_client_init(mail_task(argv[0]),
+ MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK);
+ if (chdir(var_queue_dir))
+ msg_fatal("chdir %s: %m", var_queue_dir);
+
+ /*
+ * All file/directory updates must be done as the mail system owner. This
+ * is because Postfix daemons manipulate the queue with those same
+ * privileges, so directories must be created with the right ownership.
+ *
+ * Running as a non-root user is also required for security reasons. When
+ * the Postfix queue hierarchy is compromised, an attacker could trick us
+ * into entering other file hierarchies and afflicting damage. Running as
+ * a non-root user limits the damage to the already compromised mail
+ * owner.
+ */
+ set_ugid(var_owner_uid, var_owner_gid);
+
+ /*
+ * Be sure to log a warning if we do not finish structural repair. Maybe
+ * we should have an fsck-style "clean" flag so Postfix will not start
+ * with a broken queue.
+ *
+ * Set up signal handlers after permanently dropping super-user privileges,
+ * so that signal handlers will always run with the correct privileges.
+ *
+ * XXX Don't enable SIGHUP or SIGTERM if it was ignored by the parent.
+ *
+ * interrupted() uses the in-kernel SIGINT handler address as an atomic
+ * variable to prevent nested interrupted() calls. For this reason, the
+ * SIGINT handler must be configured before other signal handlers are
+ * allowed to invoke interrupted(). See also similar code in postdrop.
+ */
+ signal(SIGINT, interrupted);
+ signal(SIGQUIT, interrupted);
+ if (signal(SIGTERM, SIG_IGN) == SIG_DFL)
+ signal(SIGTERM, interrupted);
+ if (signal(SIGHUP, SIG_IGN) == SIG_DFL)
+ signal(SIGHUP, interrupted);
+ msg_cleanup(fatal_warning);
+
+ /*
+ * Execute the explicitly specified (or default) action, on the
+ * explicitly specified (or default) queues.
+ *
+ * XXX Work around gcc const brain damage.
+ *
+ * XXX The file name/inode number fix should always run over all message
+ * file directories, and should always be preceded by a subdirectory
+ * level check of the bounce and defer logfile directories.
+ */
+ if (action == 0)
+ action = ACTION_DEFAULT;
+ if (argv[optind] != 0)
+ queues = (const char **) argv + optind;
+ else
+ queues = (const char **) default_queues;
+
+ /*
+ * Basic queue maintenance, including mass name-to-inode fixing. This
+ * ensures that queue files are in the right place before any other
+ * operations are done.
+ */
+ if (action & ACTIONS_FOR_REPAIR)
+ super(queues, action & ACTIONS_FOR_REPAIR);
+
+ /*
+ * If any file names needed changing to match the message file inode
+ * number, those files were named newqeueid#FIX. We need a second pass to
+ * strip the suffix from the new queue ID, and to complete any requested
+ * operations that had to be skipped in the first pass.
+ */
+ if (inode_mismatch > 0)
+ super(queues, action & ACTIONS_FOR_REPAIR);
+
+ /*
+ * Don't do actions by queue file name if any queue files changed name
+ * because they did not match the queue file inode number. We could be
+ * acting on the wrong queue file and lose mail.
+ */
+ if ((action & ACTIONS_BY_QUEUE_ID)
+ && (inode_mismatch > 0 || inode_fixed > 0)) {
+ msg_error("QUEUE FILE NAMES WERE CHANGED TO MATCH INODE NUMBERS");
+ msg_fatal("CHECK YOUR QUEUE IDS AND RE-ISSUE THE COMMAND");
+ }
+
+ /*
+ * Execute actions by queue ID and by wildcard in the user-specified
+ * order.
+ */
+ optind = saved_optind;
+ while ((c = GETOPT(argc, argv, GETOPT_LIST)) > 0) {
+ switch (c) {
+ default:
+ msg_panic("%s: unexpected option: %c", argv[0], c);
+ case 'c':
+ case 'p':
+ case 'S':
+ case 's':
+ case 'v':
+ /* Already handled. */
+ break;
+ case 'd':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_DELETE_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, delete_one, queues);
+ else
+ delete_one(queues, optarg);
+ break;
+ case 'e':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_EXPIRE_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, expire_one, queues);
+ else
+ expire_one(queues, optarg);
+ break;
+ case 'f':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_EXP_REL_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, exp_rel_one, queues);
+ else
+ exp_rel_one(queues, optarg);
+ break;
+ case 'h':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_HOLD_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, hold_one, queues);
+ else
+ hold_one(queues, optarg);
+ break;
+ case 'H':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_RELEASE_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, release_one, queues);
+ else
+ release_one(queues, optarg);
+ break;
+ case 'r':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_REQUEUE_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, requeue_one, queues);
+ else
+ requeue_one(queues, optarg);
+ break;
+ }
+ }
+
+ /*
+ * Report.
+ */
+ if (action & (ACTION_REQUEUE_ONE | ACTION_REQUEUE_ALL))
+ msg_info("Requeued: %d message%s", message_requeued,
+ message_requeued != 1 ? "s" : "");
+ if (action & (ACTION_DELETE_ONE | ACTION_DELETE_ALL))
+ msg_info("Deleted: %d message%s", message_deleted,
+ message_deleted != 1 ? "s" : "");
+ if (action & (ACTION_EXPIRE_ONE | ACTION_EXPIRE_ALL
+ | ACTION_EXP_REL_ONE | ACTION_EXP_REL_ALL))
+ msg_info("Force-expired: %d message%s", message_expired,
+ message_expired != 1 ? "s" : "");
+ if (action & (ACTION_HOLD_ONE | ACTION_HOLD_ALL))
+ msg_info("Placed on hold: %d message%s",
+ message_held, message_held != 1 ? "s" : "");
+ if (action & (ACTION_RELEASE_ONE | ACTION_RELEASE_ALL
+ | ACTION_EXP_REL_ONE | ACTION_EXP_REL_ALL))
+ msg_info("Released from hold: %d message%s",
+ message_released, message_released != 1 ? "s" : "");
+ if (inode_fixed > 0)
+ msg_info("Renamed to match inode number: %d message%s", inode_fixed,
+ inode_fixed != 1 ? "s" : "");
+ if (inode_mismatch > 0 || inode_fixed > 0)
+ msg_warn("QUEUE FILE NAMES WERE CHANGED TO MATCH INODE NUMBERS");
+
+ exit(0);
+}