NetBSD/gnu/dist/postfix/postsuper/postsuper.c

386 lines
11 KiB
C

/*++
/* NAME
/* postsuper 1
/* SUMMARY
/* Postfix super intendent
/* SYNOPSIS
/* .fi
/* \fBpostsuper\fR [\fB-p\fR] [\fB-s\fR] [\fB-v\fR] [\fIdirectory ...\fR]
/* DESCRIPTION
/* The \fBpostsuper\fR command does small maintenance jobs on the named
/* Postfix queue directories (default: all).
/* Directory names are relative to the Postfix top-level queue directory.
/*
/* By default, \fBpostsuper\fR performs the operations requested with the
/* \fB-s\fR and \fB-p\fR command-line options.
/* \fBpostsuper\fR always tries to remove objects that are neither files
/* nor directories. Use of this command is restricted to the super-user.
/*
/* Options:
/* .IP \fB-s\fR
/* Structure check. Move queue files that are in the wrong place
/* in the file system hierarchy and remove subdirectories that are
/* no longer needed. File rearrangements are necessary after a change
/* in the \fBhash_queue_names\fR and/or \fBhash_queue_depth\fR
/* configuration parameters. It is highly recommended to run this
/* check once before Postfix startup.
/* .IP \fB-p\fR
/* Purge stale files (files that are left over after system or
/* software crashes).
/* .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.
/* CONFIGURATION PARAMETERS
/* .ad
/* .fi
/* See the Postfix \fBmain.cf\fR file for syntax details and for
/* default values.
/* .IP \fBhash_queue_depth\fR
/* Number of subdirectory levels for hashed queues.
/* .IP \fBhash_queue_names\fR
/* The names of queues that are organized into multiple levels of
/* subdirectories.
/* 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
/*--*/
/* System library. */
#include <sys_defs.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdio.h> /* remove() */
/* Utility library. */
#include <mymalloc.h>
#include <msg.h>
#include <msg_syslog.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>
/* Global library. */
#include <mail_task.h>
#include <mail_conf.h>
#include <mail_params.h>
#include <mail_queue.h>
/* Application-specific. */
#define MAX_TEMP_AGE (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_DEFAULT (ACTION_STRUCT | ACTION_PURGE)
/*
* 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_DEFER, 0600, RECURSE,
MAIL_QUEUE_BOUNCE, 0600, RECURSE,
0,
};
/* super - check queue file location and clean up */
static void super(char **queues, int action)
{
ARGV *hash_queue_names = argv_split(var_hash_queue_names, " \t\r\n,");
VSTRING *actual_path = vstring_alloc(10);
VSTRING *wanted_path = vstring_alloc(10);
struct stat st;
char *queue_name;
SCAN_DIR *info;
char *path;
int actual_depth;
int wanted_depth;
char **cpp;
struct queue_info *qp;
/*
* 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) {
/*
* 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.
*/
for (qp = queue_info; /* void */ ; qp++) {
if (qp->name == 0)
msg_fatal("unknown queue name: %s", queue_name);
if (strcmp(qp->name, queue_name) == 0)
break;
}
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) {
if (rmdir(scan_dir_path(info)) < 0 && errno != ENOENT)
msg_warn("remove %s: %m", scan_dir_path(info));
else if (msg_verbose)
msg_info("remove %s", 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;
}
/*
* See if this is a stale file or some non-file object. Be
* careful not to delete bounce or defer logs just because they
* are more than a couple days old.
*/
vstring_sprintf(actual_path, "%s/%s", scan_dir_path(info), path);
if (stat(STR(actual_path), &st) < 0)
continue;
if (S_ISDIR(st.st_mode)) {
if (rmdir(STR(actual_path)) < 0 && errno != ENOENT)
msg_warn("remove subdirectory %s: %m", STR(actual_path));
else if (msg_verbose)
msg_info("remove subdirectory %s", STR(actual_path));
continue;
}
if (!S_ISREG(st.st_mode)
|| ((action & ACTION_PURGE) != 0 &&
(st.st_mode & S_IRWXU) != qp->perms
&& time((time_t *) 0) > st.st_mtime + MAX_TEMP_AGE)) {
if (remove(STR(actual_path)) < 0 && errno != ENOENT)
msg_warn("remove %s: %m", STR(actual_path));
else if (msg_verbose)
msg_info("remove %s", STR(actual_path));
continue;
}
/*
* Skip temporary files that aren't old enough.
*/
if (mail_queue_id_ok(path) == 0)
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. The implied
* mkdir() operation is the main reason for this command to run
* with postfix privilege. The mail_queue_mkdirs() routine could
* be fixed to use the "right" privilege, but it is a good idea
* to do everying with the postfix owner privileges regardless,
* in order to limit the amount of damage that we can do.
*/
(void) mail_queue_path(wanted_path, queue_name, path);
if (strcmp(STR(actual_path), STR(wanted_path)) != 0) {
if (rename(STR(actual_path), STR(wanted_path)) < 0)
if (errno != ENOENT
|| mail_queue_mkdirs(STR(wanted_path)) < 0
|| rename(STR(actual_path), STR(wanted_path)) < 0)
msg_fatal("rename %s to %s: %m", STR(actual_path),
STR(wanted_path));
if (msg_verbose)
msg_info("rename %s to %s", STR(actual_path),
STR(wanted_path));
}
}
scan_dir_close(info);
}
/*
* Clean up.
*/
vstring_free(wanted_path);
vstring_free(actual_path);
argv_free(hash_queue_names);
}
main(int argc, char **argv)
{
int fd;
struct stat st;
char *slash;
int debug_me = 0;
int action = 0;
char **queues;
int c;
/*
* Defaults.
*/
static char *default_queues[] = {
MAIL_QUEUE_MAILDROP,
MAIL_QUEUE_INCOMING,
MAIL_QUEUE_ACTIVE,
MAIL_QUEUE_DEFERRED,
MAIL_QUEUE_DEFER,
MAIL_QUEUE_BOUNCE,
0,
};
/*
* 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 environment options as early as we can. We might be called
* from a set-uid (set-gid) program, so be careful with importing
* environment variables.
*/
if (safe_getenv(CONF_ENV_VERB))
msg_verbose = 1;
if (safe_getenv(CONF_ENV_DEBUG))
debug_me = 1;
/*
* Initialize. Set up logging, read the global configuration file and
* extract configuration information.
*/
if ((slash = strrchr(argv[0], '/')) != 0)
argv[0] = slash + 1;
msg_vstream_init(argv[0], VSTREAM_ERR);
msg_syslog_init(mail_task(argv[0]), LOG_PID, LOG_FACILITY);
set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0]));
mail_conf_read();
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);
/*
* Parse JCL.
*/
while ((c = GETOPT(argc, argv, "spv")) > 0) {
switch (c) {
default:
msg_fatal("usage: %s [-s (fix structure)] [-p (purge stale files)]",
argv[0]);
case 's':
action |= ACTION_STRUCT;
break;
case 'p':
action |= ACTION_PURGE;
break;
case 'v':
msg_verbose++;
break;
}
}
/*
* Execute the explicitly specified (or default) action, on the
* explicitly specified (or default) queues.
*/
if (action == 0)
action = ACTION_DEFAULT;
if (argv[optind] == 0)
queues = default_queues;
else
queues = argv + optind;
super(queues, action);
exit(0);
}