NetBSD/gnu/dist/postfix/postdrop/postdrop.c

297 lines
8.3 KiB
C

/*++
/* NAME
/* postdrop 1
/* SUMMARY
/* Postfix mail posting agent
/* SYNOPSIS
/* \fBpostdrop\fR [\fIoption ...\fR]
/* DESCRIPTION
/* The \fBpostdrop\fR command creates a file in the \fBmaildrop\fR
/* directory and copies its standard input to the file.
/*
/* The command is designed to run with set-gid privileges, and with
/* group write permission to the \fBmaildrop\fR queue directory.
/*
/* The \fBpostdrop\fR command is automatically invoked by the
/* \fBsendmail\fR(1) mail posting agent when the \fBmaildrop\fR
/* queue directory is not world-writable.
/*
/* Options:
/* .IP \fB-v\fR
/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR
/* options make the software increasingly verbose.
/* SECURITY
/* .ad
/* .fi
/* This program is designed so that it can run with set-user (or
/* group) id privileges.
/* DIAGNOSTICS
/* Fatal errors: malformed input, I/O error, out of memory. Problems
/* are logged to \fBsyslogd\fR(8) and to the standard error stream.
/* When the input is incomplete, or when the process receives a HUP,
/* INT, QUIT or TERM signal, the queue file is deleted.
/* ENVIRONMENT
/* .ad
/* .fi
/* The program deletes all environment information, because the C
/* library can't be trusted.
/* FILES
/* /var/spool/postfix, mail queue
/* /etc/postfix, configuration files
/* CONFIGURATION PARAMETERS
/* .ad
/* .fi
/* See the Postfix \fBmain.cf\fR file for syntax details and for
/* default values. Use the \fBpostfix reload\fR command after a
/* configuration change.
/* .IP \fBqueue_directory\fR
/* Top-level directory of the Postfix queue. This is also the root
/* directory of Postfix daemons that run chrooted.
/* SEE ALSO
/* sendmail(1) compatibility interface
/* 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
/*--*/
/* System library. */
#include <sys_defs.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h> /* remove() */
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <syslog.h>
/* Utility library. */
#include <msg.h>
#include <mymalloc.h>
#include <vstream.h>
#include <vstring.h>
#include <msg_vstream.h>
#include <msg_syslog.h>
/* Global library. */
#include <mail_proto.h>
#include <mail_queue.h>
#include <mail_params.h>
#include <mail_conf.h>
#include <mail_task.h>
#include <clean_env.h>
#include <mail_stream.h>
#include <cleanup_user.h>
#include <record.h>
#include <rec_type.h>
/* Application-specific. */
/*
* WARNING WARNING WARNING
*
* This software is designed to run set-gid on systems that cannot afford a
* world-writable spool directory. In order to make this restriction work,
* this software should not run any external commands, nor should it take
* any configuration information from the user.
*/
/*
* Queue file name. Global, so that the cleanup routine can find it when
* called by the run-time error handler.
*/
static char *postdrop_path;
/* postdrop_cleanup - callback for the runtime error handler */
static void postdrop_cleanup(void)
{
/*
* This is the fatal error handler. Don't try to do anything fancy.
*/
if (postdrop_path) {
if (remove(postdrop_path))
msg_warn("uid=%d: remove %s: %m", getuid(), postdrop_path);
else if (msg_verbose)
msg_info("remove %s", postdrop_path);
postdrop_path = 0;
}
}
/* postdrop_sig - catch signal and clean up */
static void postdrop_sig(int sig)
{
postdrop_cleanup();
exit(sig);
}
/* main - the main program */
int main(int argc, char **argv)
{
struct stat st;
int fd;
int c;
VSTRING *buf;
int status;
MAIL_STREAM *dst;
int rec_type;
static char *segment_info[] = {
REC_TYPE_ENVELOPE, REC_TYPE_CONTENT, REC_TYPE_EXTRACT,
};
char **expected;
uid_t uid = getuid();
/*
* 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");
/*
* Strip the environment so we don't have to trust the C library.
*/
clean_env();
/*
* Set up logging. Censor the process name: it is provided by the user.
*/
argv[0] = "postdrop";
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]));
/*
* Read the global configuration file and extract configuration
* information. Some claim that the user should supply the working
* directory instead. That might be OK, given that this command needs
* write permission in a subdirectory called "maildrop". However we still
* need to reliably detect incomplete input, and so we must perform
* record-level I/O. With that, we should also take the opportunity to
* perform some sanity checks on the input.
*/
mail_conf_read();
if (chdir(var_queue_dir))
msg_fatal("chdir %s: %m", var_queue_dir);
if (msg_verbose)
msg_info("chdir %s", var_queue_dir);
/*
* Set up signal handlers and a runtime error handler so that we can
* clean up incomplete output.
*/
signal(SIGPIPE, SIG_IGN);
signal(SIGHUP, postdrop_sig);
signal(SIGINT, postdrop_sig);
signal(SIGQUIT, postdrop_sig);
signal(SIGTERM, postdrop_sig);
msg_cleanup(postdrop_cleanup);
/*
* Parse JCL.
*/
while ((c = GETOPT(argc, argv, "v")) > 0) {
switch (c) {
case 'v':
msg_verbose++;
break;
default:
msg_fatal("usage: %s [-v]", argv[0]);
}
}
/*
* Create queue file. mail_stream_file() never fails. Send the queue ID
* to the caller. Stash away a copy of the queue file name so we can
* clean up in case of a fatal error or an interrupt.
*/
dst = mail_stream_file(MAIL_QUEUE_MAILDROP, MAIL_CLASS_PUBLIC,
MAIL_SERVICE_PICKUP);
mail_print(VSTREAM_OUT, "%s", dst->id);
vstream_fflush(VSTREAM_OUT);
postdrop_path = mystrdup(VSTREAM_PATH(dst->stream));
/*
* Copy stdin to file. The format is checked so that we can recognize
* incomplete input and cancel the operation. With the sanity checks
* applied here, the pickup daemon could skip format checks and pass a
* file descriptor to the cleanup daemon. These are by no means all
* sanity checks - the cleanup service and queue manager services will
* reject messages that lack required information.
*/
vstream_control(VSTREAM_IN, VSTREAM_CTL_PATH, "stdin", VSTREAM_CTL_END);
buf = vstring_alloc(100);
expected = segment_info;
for (;;) {
rec_type = rec_get(VSTREAM_IN, buf, var_line_limit);
if (rec_type == REC_TYPE_EOF) { /* request cancelled */
mail_stream_cleanup(dst);
if (remove(postdrop_path))
msg_warn("uid=%d: remove %s: %m", getuid(), postdrop_path);
else if (msg_verbose)
msg_info("remove %s", postdrop_path);
myfree(postdrop_path);
postdrop_path = 0;
exit(0);
}
if (rec_type == REC_TYPE_ERROR)
msg_fatal("uid=%d: malformed input", uid);
if (rec_type == REC_TYPE_TIME)
rec_fprintf(dst->stream, REC_TYPE_TIME, "%ld",
(long) time((time_t *) 0));
if (strchr(*expected, rec_type) == 0)
msg_fatal("uid=%d: unexpected record type: %d", uid, rec_type);
if (rec_type == **expected)
expected++;
if (REC_PUT_BUF(dst->stream, rec_type, buf) < 0)
msg_fatal("uid=%d: queue file write error", uid);
if (rec_type == REC_TYPE_END)
break;
}
vstring_free(buf);
/*
* Finish the file.
*/
if ((status = mail_stream_finish(dst)) != 0)
msg_fatal("uid=%d: %s", uid, cleanup_strerror(status));
/*
* Disable deletion on fatal error before reporting success, so the file
* will not be deleted after we have taken responsibility for delivery.
*/
if (postdrop_path) {
myfree(postdrop_path);
postdrop_path = 0;
}
/*
* Send the completion status to the caller and terminate.
*/
mail_print(VSTREAM_OUT, "%d", status);
vstream_fflush(VSTREAM_OUT);
exit(status);
}