diff options
Diffstat (limited to '')
l--------- | src/pipe/.indent.pro | 1 | ||||
-rw-r--r-- | src/pipe/.printfck | 25 | ||||
-rw-r--r-- | src/pipe/Makefile.in | 107 | ||||
-rw-r--r-- | src/pipe/pipe.c | 1392 |
4 files changed, 1525 insertions, 0 deletions
diff --git a/src/pipe/.indent.pro b/src/pipe/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/pipe/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro
\ No newline at end of file diff --git a/src/pipe/.printfck b/src/pipe/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/pipe/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/pipe/Makefile.in b/src/pipe/Makefile.in new file mode 100644 index 0000000..42159ca --- /dev/null +++ b/src/pipe/Makefile.in @@ -0,0 +1,107 @@ +SHELL = /bin/sh +SRCS = pipe.c +OBJS = pipe.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = pipe +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +pipe.o: ../../include/argv.h +pipe.o: ../../include/attr.h +pipe.o: ../../include/bounce.h +pipe.o: ../../include/canon_addr.h +pipe.o: ../../include/check_arg.h +pipe.o: ../../include/defer.h +pipe.o: ../../include/deliver_completed.h +pipe.o: ../../include/deliver_request.h +pipe.o: ../../include/delivered_hdr.h +pipe.o: ../../include/dict.h +pipe.o: ../../include/dsn.h +pipe.o: ../../include/dsn_buf.h +pipe.o: ../../include/dsn_util.h +pipe.o: ../../include/flush_clnt.h +pipe.o: ../../include/fold_addr.h +pipe.o: ../../include/htable.h +pipe.o: ../../include/iostuff.h +pipe.o: ../../include/mac_parse.h +pipe.o: ../../include/mail_addr.h +pipe.o: ../../include/mail_conf.h +pipe.o: ../../include/mail_copy.h +pipe.o: ../../include/mail_params.h +pipe.o: ../../include/mail_parm_split.h +pipe.o: ../../include/mail_server.h +pipe.o: ../../include/mail_version.h +pipe.o: ../../include/msg.h +pipe.o: ../../include/msg_stats.h +pipe.o: ../../include/myflock.h +pipe.o: ../../include/mymalloc.h +pipe.o: ../../include/nvtable.h +pipe.o: ../../include/off_cvt.h +pipe.o: ../../include/pipe_command.h +pipe.o: ../../include/quote_822_local.h +pipe.o: ../../include/quote_flags.h +pipe.o: ../../include/recipient_list.h +pipe.o: ../../include/sent.h +pipe.o: ../../include/set_eugid.h +pipe.o: ../../include/split_addr.h +pipe.o: ../../include/split_at.h +pipe.o: ../../include/stringops.h +pipe.o: ../../include/sys_defs.h +pipe.o: ../../include/sys_exits.h +pipe.o: ../../include/vbuf.h +pipe.o: ../../include/vstream.h +pipe.o: ../../include/vstring.h +pipe.o: pipe.c diff --git a/src/pipe/pipe.c b/src/pipe/pipe.c new file mode 100644 index 0000000..79d4159 --- /dev/null +++ b/src/pipe/pipe.c @@ -0,0 +1,1392 @@ +/*++ +/* NAME +/* pipe 8 +/* SUMMARY +/* Postfix delivery to external command +/* SYNOPSIS +/* \fBpipe\fR [generic Postfix daemon options] command_attributes... +/* DESCRIPTION +/* The \fBpipe\fR(8) daemon processes requests from the Postfix queue +/* manager to deliver messages to external commands. +/* This program expects to be run from the \fBmaster\fR(8) process +/* manager. +/* +/* Message attributes such as sender address, recipient address and +/* next-hop host name can be specified as command-line macros that are +/* expanded before the external command is executed. +/* +/* The \fBpipe\fR(8) daemon updates queue files and marks recipients +/* as finished, or it informs the queue manager that delivery should +/* be tried again at a later time. Delivery status reports are sent +/* to the \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemon as +/* appropriate. +/* SINGLE-RECIPIENT DELIVERY +/* .ad +/* .fi +/* Some destinations cannot handle more than one recipient per +/* delivery request. Examples are pagers or fax machines. +/* In addition, multi-recipient delivery is undesirable when +/* prepending a \fBDelivered-to:\fR or \fBX-Original-To:\fR +/* message header. +/* +/* To prevent Postfix from sending multiple recipients per delivery +/* request, specify +/* .sp +/* .nf +/* \fItransport\fB_destination_recipient_limit = 1\fR +/* .fi +/* +/* in the Postfix \fBmain.cf\fR file, where \fItransport\fR +/* is the name in the first column of the Postfix \fBmaster.cf\fR +/* entry for the pipe-based delivery transport. +/* COMMAND ATTRIBUTE SYNTAX +/* .ad +/* .fi +/* The external command attributes are given in the \fBmaster.cf\fR +/* file at the end of a service definition. The syntax is as follows: +/* .IP "\fBchroot=\fIpathname\fR (optional)" +/* Change the process root directory and working directory to +/* the named directory. This happens before switching to the +/* privileges specified with the \fBuser\fR attribute, and +/* before executing the optional \fBdirectory=\fIpathname\fR +/* directive. Delivery is deferred in case of failure. +/* .sp +/* This feature is available as of Postfix 2.3. +/* .IP "\fBdirectory=\fIpathname\fR (optional)" +/* Change to the named directory before executing the external command. +/* The directory must be accessible for the user specified with the +/* \fBuser\fR attribute (see below). +/* The default working directory is \fB$queue_directory\fR. +/* Delivery is deferred in case of failure. +/* .sp +/* This feature is available as of Postfix 2.2. +/* .IP "\fBeol=\fIstring\fR (optional, default: \fB\en\fR)" +/* The output record delimiter. Typically one would use either +/* \fB\er\en\fR or \fB\en\fR. The usual C-style backslash escape +/* sequences are recognized: \fB\ea \eb \ef \en \er \et \ev +/* \e\fIddd\fR (up to three octal digits) and \fB\e\e\fR. +/* .IP "\fBflags=BDFORXhqu.>\fR (optional)" +/* Optional message processing flags. By default, a message is +/* copied unchanged. +/* .RS +/* .IP \fBB\fR +/* Append a blank line at the end of each message. This is required +/* by some mail user agents that recognize "\fBFrom \fR" lines only +/* when preceded by a blank line. +/* .IP \fBD\fR +/* Prepend a "\fBDelivered-To: \fIrecipient\fR" message header with the +/* envelope recipient address. Note: for this to work, the +/* \fItransport\fB_destination_recipient_limit\fR must be 1 +/* (see SINGLE-RECIPIENT DELIVERY above for details). +/* .sp +/* The \fBD\fR flag also enforces loop detection (Postfix 2.5 and later): +/* if a message already contains a \fBDelivered-To:\fR header +/* with the same recipient address, then the message is +/* returned as undeliverable. The address comparison is case +/* insensitive. +/* .sp +/* This feature is available as of Postfix 2.0. +/* .IP \fBF\fR +/* Prepend a "\fBFrom \fIsender time_stamp\fR" envelope header to +/* the message content. +/* This is expected by, for example, \fBUUCP\fR software. +/* .IP \fBO\fR +/* Prepend an "\fBX-Original-To: \fIrecipient\fR" message header +/* with the recipient address as given to Postfix. Note: for this to +/* work, the \fItransport\fB_destination_recipient_limit\fR must be 1 +/* (see SINGLE-RECIPIENT DELIVERY above for details). +/* .sp +/* This feature is available as of Postfix 2.0. +/* .IP \fBR\fR +/* Prepend a \fBReturn-Path:\fR message header with the envelope sender +/* address. +/* .IP \fBX\fR +/* Indicate that the external command performs final delivery. +/* This flag affects the status reported in "success" DSN +/* (delivery status notification) messages, and changes it +/* from "relayed" into "delivered". +/* .sp +/* This feature is available as of Postfix 2.5. +/* .IP \fBh\fR +/* Fold the command-line \fB$original_recipient\fR and +/* \fB$recipient\fR address domain part +/* (text to the right of the right-most \fB@\fR character) to +/* lower case; fold the entire command-line \fB$domain\fR and +/* \fB$nexthop\fR host or domain information to lower case. +/* This is recommended for delivery via \fBUUCP\fR. +/* .IP \fBq\fR +/* Quote white space and other special characters in the command-line +/* \fB$sender\fR, \fB$original_recipient\fR and \fB$recipient\fR +/* address localparts (text to the +/* left of the right-most \fB@\fR character), according to an 8-bit +/* transparent version of RFC 822. +/* This is recommended for delivery via \fBUUCP\fR or \fBBSMTP\fR. +/* .sp +/* The result is compatible with the address parsing of command-line +/* recipients by the Postfix \fBsendmail\fR(1) mail submission command. +/* .sp +/* The \fBq\fR flag affects only entire addresses, not the partial +/* address information from the \fB$user\fR, \fB$extension\fR or +/* \fB$mailbox\fR command-line macros. +/* .IP \fBu\fR +/* Fold the command-line \fB$original_recipient\fR and +/* \fB$recipient\fR address localpart (text to +/* the left of the right-most \fB@\fR character) to lower case. +/* This is recommended for delivery via \fBUUCP\fR. +/* .IP \fB.\fR +/* Prepend "\fB.\fR" to lines starting with "\fB.\fR". This is needed +/* by, for example, \fBBSMTP\fR software. +/* .IP \fB>\fR +/* Prepend "\fB>\fR" to lines starting with "\fBFrom \fR". This is expected +/* by, for example, \fBUUCP\fR software. +/* .RE +/* .IP "\fBnull_sender\fR=\fIreplacement\fR (default: MAILER-DAEMON)" +/* Replace the null sender address (typically used for delivery +/* status notifications) with the specified text +/* when expanding the \fB$sender\fR command-line macro, and +/* when generating a From_ or Return-Path: message header. +/* +/* If the null sender replacement text is a non-empty string +/* then it is affected by the \fBq\fR flag for address quoting +/* in command-line arguments. +/* +/* The null sender replacement text may be empty; this form +/* is recommended for content filters that feed mail back into +/* Postfix. The empty sender address is not affected by the +/* \fBq\fR flag for address quoting in command-line arguments. +/* .sp +/* Caution: a null sender address is easily mis-parsed by +/* naive software. For example, when the \fBpipe\fR(8) daemon +/* executes a command such as: +/* .sp +/* .nf +/* \fIWrong\fR: command -f$sender -- $recipient +/* .fi +/* .IP +/* the command will mis-parse the -f option value when the +/* sender address is a null string. For correct parsing, +/* specify \fB$sender\fR as an argument by itself: +/* .sp +/* .nf +/* \fIRight\fR: command -f $sender -- $recipient +/* .fi +/* .IP +/* This feature is available as of Postfix 2.3. +/* .IP "\fBsize\fR=\fIsize_limit\fR (optional)" +/* Don't deliver messages that exceed this size limit (in +/* bytes); return them to the sender instead. +/* .IP "\fBuser\fR=\fIusername\fR (required)" +/* .IP "\fBuser\fR=\fIusername\fR:\fIgroupname\fR" +/* Execute the external command with the user ID and group ID of the +/* specified \fIusername\fR. The software refuses to execute +/* commands with root privileges, or with the privileges of the +/* mail system owner. If \fIgroupname\fR is specified, the +/* corresponding group ID is used instead of the group ID of +/* \fIusername\fR. +/* .IP "\fBargv\fR=\fIcommand\fR... (required)" +/* The command to be executed. This must be specified as the +/* last command attribute. +/* The command is executed directly, i.e. without interpretation of +/* shell meta characters by a shell command interpreter. +/* .sp +/* Specify "{" and "}" around command arguments that contain +/* whitespace (Postfix 3.0 and later). Whitespace +/* after the opening "{" and before the closing "}" is ignored. +/* .sp +/* In the command argument vector, the following macros are recognized +/* and replaced with corresponding information from the Postfix queue +/* manager delivery request. +/* .sp +/* In addition to the form ${\fIname\fR}, the forms $\fIname\fR and +/* the deprecated form $(\fIname\fR) are also recognized. +/* Specify \fB$$\fR where a single \fB$\fR is wanted. +/* .RS +/* .IP \fB${client_address}\fR +/* This macro expands to the remote client network address. +/* .sp +/* This feature is available as of Postfix 2.2. +/* .IP \fB${client_helo}\fR +/* This macro expands to the remote client HELO command parameter. +/* .sp +/* This feature is available as of Postfix 2.2. +/* .IP \fB${client_hostname}\fR +/* This macro expands to the remote client hostname. +/* .sp +/* This feature is available as of Postfix 2.2. +/* .IP \fB${client_port}\fR +/* This macro expands to the remote client TCP port number. +/* .sp +/* This feature is available as of Postfix 2.5. +/* .IP \fB${client_protocol}\fR +/* This macro expands to the remote client protocol. +/* .sp +/* This feature is available as of Postfix 2.2. +/* .IP \fB${domain}\fR +/* This macro expands to the domain portion of the recipient +/* address. For example, with an address \fIuser+foo@domain\fR +/* the domain is \fIdomain\fR. +/* .sp +/* This information is modified by the \fBh\fR flag for case folding. +/* .sp +/* This feature is available as of Postfix 2.5. +/* .IP \fB${extension}\fR +/* This macro expands to the extension part of a recipient address. +/* For example, with an address \fIuser+foo@domain\fR the extension is +/* \fIfoo\fR. +/* .sp +/* A command-line argument that contains \fB${extension}\fR expands +/* into as many command-line arguments as there are recipients. +/* .sp +/* This information is modified by the \fBu\fR flag for case folding. +/* .IP \fB${mailbox}\fR +/* This macro expands to the complete local part of a recipient address. +/* For example, with an address \fIuser+foo@domain\fR the mailbox is +/* \fIuser+foo\fR. +/* .sp +/* A command-line argument that contains \fB${mailbox}\fR +/* expands to as many command-line arguments as there are recipients. +/* .sp +/* This information is modified by the \fBu\fR flag for case folding. +/* .IP \fB${nexthop}\fR +/* This macro expands to the next-hop hostname. +/* .sp +/* This information is modified by the \fBh\fR flag for case folding. +/* .IP \fB${original_recipient}\fR +/* This macro expands to the complete recipient address before any +/* address rewriting or aliasing. +/* .sp +/* A command-line argument that contains +/* \fB${original_recipient}\fR expands to as many +/* command-line arguments as there are recipients. +/* .sp +/* This information is modified by the \fBhqu\fR flags for quoting +/* and case folding. +/* .sp +/* This feature is available as of Postfix 2.5. +/* .IP \fB${queue_id}\fR +/* This macro expands to the queue id. +/* .sp +/* This feature is available as of Postfix 2.11. +/* .IP \fB${recipient}\fR +/* This macro expands to the complete recipient address. +/* .sp +/* A command-line argument that contains \fB${recipient}\fR +/* expands to as many command-line arguments as there are recipients. +/* .sp +/* This information is modified by the \fBhqu\fR flags for quoting +/* and case folding. +/* .IP \fB${sasl_method}\fR +/* This macro expands to the name of the SASL authentication +/* mechanism in the AUTH command when the Postfix SMTP server +/* received the message. +/* .sp +/* This feature is available as of Postfix 2.2. +/* .IP \fB${sasl_sender}\fR +/* This macro expands to the SASL sender name (i.e. the original +/* submitter as per RFC 4954) in the MAIL FROM command when +/* the Postfix SMTP server received the message. +/* .sp +/* This feature is available as of Postfix 2.2. +/* .IP \fB${sasl_username}\fR +/* This macro expands to the SASL user name in the AUTH command +/* when the Postfix SMTP server received the message. +/* .sp +/* This feature is available as of Postfix 2.2. +/* .IP \fB${sender}\fR +/* This macro expands to the envelope sender address. By default, +/* the null sender address expands to MAILER-DAEMON; this can +/* be changed with the \fBnull_sender\fR attribute, as described +/* above. +/* .sp +/* This information is modified by the \fBq\fR flag for quoting. +/* .IP \fB${size}\fR +/* This macro expands to Postfix's idea of the message size, which +/* is an approximation of the size of the message as delivered. +/* .IP \fB${user}\fR +/* This macro expands to the username part of a recipient address. +/* For example, with an address \fIuser+foo@domain\fR the username +/* part is \fIuser\fR. +/* .sp +/* A command-line argument that contains \fB${user}\fR expands +/* into as many command-line arguments as there are recipients. +/* .sp +/* This information is modified by the \fBu\fR flag for case folding. +/* .RE +/* STANDARDS +/* RFC 3463 (Enhanced status codes) +/* DIAGNOSTICS +/* Command exit status codes are expected to +/* follow the conventions defined in <\fBsysexits.h\fR>. +/* Exit status 0 means normal successful completion. +/* +/* In the case of a non-zero exit status, a limited amount of +/* command output is logged, and reported in a delivery status +/* notification. When the output begins with a 4.X.X or 5.X.X +/* enhanced status code, the status code takes precedence over +/* the non-zero exit status (Postfix version 2.3 and later). +/* +/* After successful delivery (zero exit status) a limited +/* amount of command output is logged, and reported in "success" +/* delivery status notifications (Postfix 3.0 and later). +/* This command output is not examined for the presence of an +/* enhanced status code. +/* +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* Corrupted message files are marked so that the queue manager +/* can move them to the \fBcorrupt\fR queue for further inspection. +/* SECURITY +/* .fi +/* .ad +/* This program needs a dual personality 1) to access the private +/* Postfix queue and IPC mechanisms, and 2) to execute external +/* commands as the specified user. It is therefore security sensitive. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically as \fBpipe\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* In the text below, \fItransport\fR is the first field in a +/* \fBmaster.cf\fR entry. +/* .IP "\fBtransport_time_limit ($command_time_limit)\fR" +/* A transport-specific override for the command_time_limit parameter +/* value, where \fItransport\fR is the master.cf name of the message +/* delivery transport. +/* .PP +/* Implemented in the qmgr(8) daemon: +/* .IP "\fBtransport_destination_concurrency_limit ($default_destination_concurrency_limit)\fR" +/* A transport-specific override for the +/* default_destination_concurrency_limit parameter value, where +/* \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* .IP "\fBtransport_destination_recipient_limit ($default_destination_recipient_limit)\fR" +/* A transport-specific override for the +/* default_destination_recipient_limit parameter value, where +/* \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBdelay_logging_resolution_limit (2)\fR" +/* The maximal number of digits after the decimal point when logging +/* sub-second delay values. +/* .IP "\fBexport_environment (see 'postconf -d' output)\fR" +/* The list of environment variables that a Postfix process will export +/* to non-Postfix processes. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmail_owner (postfix)\fR" +/* The UNIX system account that owns the Postfix queue and most Postfix +/* daemon processes. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBrecipient_delimiter (empty)\fR" +/* The set of characters that can separate a user name from its +/* extension (example: user+foo), or a .forward file name from its +/* extension (example: .forward+foo). +/* .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 3.0 and later: +/* .IP "\fBpipe_delivery_status_filter ($default_delivery_status_filter)\fR" +/* Optional filter for the \fBpipe\fR(8) delivery agent to change the +/* delivery status code or explanatory text of successful or unsuccessful +/* deliveries. +/* .PP +/* Available in Postfix version 3.3 and later: +/* .IP "\fBenable_original_recipient (yes)\fR" +/* Enable support for the original recipient address after an +/* address is rewritten to a different address (for example with +/* aliasing or with canonical mapping). +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* qmgr(8), queue manager +/* bounce(8), delivery status reports +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* 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 <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <pwd.h> +#include <grp.h> +#include <fcntl.h> +#include <ctype.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> +#include <vstring.h> +#include <argv.h> +#include <htable.h> +#include <dict.h> +#include <iostuff.h> +#include <mymalloc.h> +#include <mac_parse.h> +#include <set_eugid.h> +#include <split_at.h> +#include <stringops.h> + +/* Global library. */ + +#include <recipient_list.h> +#include <deliver_request.h> +#include <mail_params.h> +#include <mail_version.h> +#include <mail_conf.h> +#include <bounce.h> +#include <defer.h> +#include <deliver_completed.h> +#include <sent.h> +#include <pipe_command.h> +#include <mail_copy.h> +#include <mail_addr.h> +#include <canon_addr.h> +#include <split_addr.h> +#include <off_cvt.h> +#include <quote_822_local.h> +#include <flush_clnt.h> +#include <dsn_util.h> +#include <dsn_buf.h> +#include <sys_exits.h> +#include <delivered_hdr.h> +#include <fold_addr.h> +#include <mail_parm_split.h> + +/* Single server skeleton. */ + +#include <mail_server.h> + +/* Application-specific. */ + + /* + * The mini symbol table name and keys used for expanding macros in + * command-line arguments. + * + * XXX Update the parse_callback() routine when something gets added here, + * even when the macro is not recipient dependent. + */ +#define PIPE_DICT_TABLE "pipe_command" /* table name */ +#define PIPE_DICT_NEXTHOP "nexthop" /* key */ +#define PIPE_DICT_RCPT "recipient" /* key */ +#define PIPE_DICT_ORIG_RCPT "original_recipient" /* key */ +#define PIPE_DICT_SENDER "sender"/* key */ +#define PIPE_DICT_USER "user" /* key */ +#define PIPE_DICT_EXTENSION "extension" /* key */ +#define PIPE_DICT_MAILBOX "mailbox" /* key */ +#define PIPE_DICT_DOMAIN "domain"/* key */ +#define PIPE_DICT_SIZE "size" /* key */ +#define PIPE_DICT_CLIENT_ADDR "client_address" /* key */ +#define PIPE_DICT_CLIENT_NAME "client_hostname" /* key */ +#define PIPE_DICT_CLIENT_PORT "client_port" /* key */ +#define PIPE_DICT_CLIENT_PROTO "client_protocol" /* key */ +#define PIPE_DICT_CLIENT_HELO "client_helo" /* key */ +#define PIPE_DICT_SASL_METHOD "sasl_method" /* key */ +#define PIPE_DICT_SASL_USERNAME "sasl_username" /* key */ +#define PIPE_DICT_SASL_SENDER "sasl_sender" /* key */ +#define PIPE_DICT_QUEUE_ID "queue_id" /* key */ + + /* + * Flags used to pass back the type of special parameter found by + * parse_callback. + */ +#define PIPE_FLAG_RCPT (1<<0) +#define PIPE_FLAG_USER (1<<1) +#define PIPE_FLAG_EXTENSION (1<<2) +#define PIPE_FLAG_MAILBOX (1<<3) +#define PIPE_FLAG_DOMAIN (1<<4) +#define PIPE_FLAG_ORIG_RCPT (1<<5) + + /* + * Additional flags. These are colocated with mail_copy() flags. Allow some + * space for extension of the mail_copy() interface. + */ +#define PIPE_OPT_FOLD_BASE (16) +#define PIPE_OPT_FOLD_USER (FOLD_ADDR_USER << PIPE_OPT_FOLD_BASE) +#define PIPE_OPT_FOLD_HOST (FOLD_ADDR_HOST << PIPE_OPT_FOLD_BASE) +#define PIPE_OPT_QUOTE_LOCAL (1 << (PIPE_OPT_FOLD_BASE + 2)) +#define PIPE_OPT_FINAL_DELIVERY (1 << (PIPE_OPT_FOLD_BASE + 3)) + +#define PIPE_OPT_FOLD_ALL (FOLD_ADDR_ALL << PIPE_OPT_FOLD_BASE) +#define PIPE_OPT_FOLD_FLAGS(f) \ + (((f) & PIPE_OPT_FOLD_ALL) >> PIPE_OPT_FOLD_BASE) + + /* + * Tunable parameters. Values are taken from the config file, after + * prepending the service name to _name, and so on. + */ +int var_command_maxtime; /* You can now leave this here. */ + + /* + * Other main.cf parameters. + */ +char *var_pipe_dsn_filter; + + /* + * For convenience. Instead of passing around lists of parameters, bundle + * them up in convenient structures. + */ + + /* + * Structure for service-specific configuration parameters. + */ +typedef struct { + int time_limit; /* per-service time limit */ +} PIPE_PARAMS; + + /* + * Structure for command-line parameters. + */ +typedef struct { + char **command; /* argument vector */ + uid_t uid; /* command privileges */ + gid_t gid; /* command privileges */ + int flags; /* mail_copy() flags */ + char *exec_dir; /* working directory */ + char *chroot_dir; /* chroot directory */ + VSTRING *eol; /* output record delimiter */ + VSTRING *null_sender; /* null sender expansion */ + off_t size_limit; /* max size in bytes we will accept */ +} PIPE_ATTR; + + /* + * Structure for command-line parameter macro expansion. + */ +typedef struct { + const char *service; /* for warnings */ + int expand_flag; /* callback result */ +} PIPE_STATE; + + /* + * Silly little macros. + */ +#define STR vstring_str + +/* parse_callback - callback for mac_parse() */ + +static int parse_callback(int type, VSTRING *buf, void *context) +{ + PIPE_STATE *state = (PIPE_STATE *) context; + struct cmd_flags { + const char *name; + int flags; + }; + static struct cmd_flags cmd_flags[] = { + PIPE_DICT_NEXTHOP, 0, + PIPE_DICT_RCPT, PIPE_FLAG_RCPT, + PIPE_DICT_ORIG_RCPT, PIPE_FLAG_ORIG_RCPT, + PIPE_DICT_SENDER, 0, + PIPE_DICT_USER, PIPE_FLAG_USER, + PIPE_DICT_EXTENSION, PIPE_FLAG_EXTENSION, + PIPE_DICT_MAILBOX, PIPE_FLAG_MAILBOX, + PIPE_DICT_DOMAIN, PIPE_FLAG_DOMAIN, + PIPE_DICT_SIZE, 0, + PIPE_DICT_CLIENT_ADDR, 0, + PIPE_DICT_CLIENT_NAME, 0, + PIPE_DICT_CLIENT_PORT, 0, + PIPE_DICT_CLIENT_PROTO, 0, + PIPE_DICT_CLIENT_HELO, 0, + PIPE_DICT_SASL_METHOD, 0, + PIPE_DICT_SASL_USERNAME, 0, + PIPE_DICT_SASL_SENDER, 0, + PIPE_DICT_QUEUE_ID, 0, + 0, 0, + }; + struct cmd_flags *p; + + /* + * See if this command-line argument references a special macro. + */ + if (type == MAC_PARSE_VARNAME) { + for (p = cmd_flags; /* see below */ ; p++) { + if (p->name == 0) { + msg_warn("file %s/%s: service %s: unknown macro name: \"%s\"", + var_config_dir, MASTER_CONF_FILE, + state->service, vstring_str(buf)); + return (MAC_PARSE_ERROR); + } else if (strcmp(vstring_str(buf), p->name) == 0) { + state->expand_flag |= p->flags; + return (0); + } + } + } + return (0); +} + +/* morph_recipient - morph a recipient address */ + +static void morph_recipient(VSTRING *buf, const char *address, int flags) +{ + VSTRING *temp = vstring_alloc(100); + + /* + * Quote the recipient address as appropriate. + */ + if (flags & PIPE_OPT_QUOTE_LOCAL) + quote_822_local(temp, address); + else + vstring_strcpy(temp, address); + + /* + * Fold the recipient address as appropriate. + */ + fold_addr(buf, STR(temp), PIPE_OPT_FOLD_FLAGS(flags)); + + vstring_free(temp); +} + +/* expand_argv - expand macros in the argument vector */ + +static ARGV *expand_argv(const char *service, char **argv, + RECIPIENT_LIST *rcpt_list, int flags) +{ + VSTRING *buf = vstring_alloc(100); + ARGV *result; + char **cpp; + PIPE_STATE state; + int i; + char *ext; + char *dom; + + /* + * This appears to be simple operation (replace $name by its expansion). + * However, it becomes complex because a command-line argument that + * references $recipient must expand to as many command-line arguments as + * there are recipients (that's wat programs called by sendmail expect). + * So we parse each command-line argument, and depending on what we find, + * we either expand the argument just once, or we expand it once for each + * recipient. In either case we end up parsing the command-line argument + * twice. The amount of CPU time wasted will be negligible. + * + * Note: we can't use recursive macro expansion here, because recursion + * would screw up mail addresses that contain $ characters. + */ +#define NO 0 +#define EARLY_RETURN(x) { argv_free(result); vstring_free(buf); return (x); } + + result = argv_alloc(1); + for (cpp = argv; *cpp; cpp++) { + state.service = service; + state.expand_flag = 0; + if (mac_parse(*cpp, parse_callback, (void *) &state) & MAC_PARSE_ERROR) + EARLY_RETURN(0); + if (state.expand_flag == 0) { /* no $recipient etc. */ + argv_add(result, dict_eval(PIPE_DICT_TABLE, *cpp, NO), ARGV_END); + } else { /* contains $recipient etc. */ + for (i = 0; i < rcpt_list->len; i++) { + + /* + * This argument contains $recipient. + */ + if (state.expand_flag & PIPE_FLAG_RCPT) { + morph_recipient(buf, rcpt_list->info[i].address, flags); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_RCPT, STR(buf)); + } + + /* + * This argument contains $original_recipient. + */ + if (state.expand_flag & PIPE_FLAG_ORIG_RCPT) { + morph_recipient(buf, rcpt_list->info[i].orig_addr, flags); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_ORIG_RCPT, STR(buf)); + } + + /* + * This argument contains $user. Extract the plain user name. + * Either anything to the left of the extension delimiter or, + * in absence of the latter, anything to the left of the + * rightmost @. + * + * Beware: if the user name is blank (e.g. +user@host), the + * argument is suppressed. This is necessary to allow for + * cyrus bulletin-board (global mailbox) delivery. XXX But, + * skipping empty user parts will also prevent other + * expansions of this specific command-line argument. + */ + if (state.expand_flag & PIPE_FLAG_USER) { + morph_recipient(buf, rcpt_list->info[i].address, + flags & PIPE_OPT_FOLD_ALL); + if (split_at_right(STR(buf), '@') == 0) + msg_warn("no @ in recipient address: %s", + rcpt_list->info[i].address); + if (*var_rcpt_delim) + split_addr(STR(buf), var_rcpt_delim); + if (*STR(buf) == 0) + continue; + dict_update(PIPE_DICT_TABLE, PIPE_DICT_USER, STR(buf)); + } + + /* + * This argument contains $extension. Extract the recipient + * extension: anything between the leftmost extension + * delimiter and the rightmost @. The extension may be blank. + */ + if (state.expand_flag & PIPE_FLAG_EXTENSION) { + morph_recipient(buf, rcpt_list->info[i].address, + flags & PIPE_OPT_FOLD_ALL); + if (split_at_right(STR(buf), '@') == 0) + msg_warn("no @ in recipient address: %s", + rcpt_list->info[i].address); + if (*var_rcpt_delim == 0 + || (ext = split_addr(STR(buf), var_rcpt_delim)) == 0) + ext = ""; /* insert null arg */ + dict_update(PIPE_DICT_TABLE, PIPE_DICT_EXTENSION, ext); + } + + /* + * This argument contains $mailbox. Extract the mailbox name: + * anything to the left of the rightmost @. + */ + if (state.expand_flag & PIPE_FLAG_MAILBOX) { + morph_recipient(buf, rcpt_list->info[i].address, + flags & PIPE_OPT_FOLD_ALL); + if (split_at_right(STR(buf), '@') == 0) + msg_warn("no @ in recipient address: %s", + rcpt_list->info[i].address); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_MAILBOX, STR(buf)); + } + + /* + * This argument contains $domain. Extract the domain name: + * anything to the right of the rightmost @. + */ + if (state.expand_flag & PIPE_FLAG_DOMAIN) { + morph_recipient(buf, rcpt_list->info[i].address, + flags & PIPE_OPT_FOLD_ALL); + dom = split_at_right(STR(buf), '@'); + if (dom == 0) { + msg_warn("no @ in recipient address: %s", + rcpt_list->info[i].address); + dom = ""; /* insert null arg */ + } + dict_update(PIPE_DICT_TABLE, PIPE_DICT_DOMAIN, dom); + } + + /* + * Done. + */ + argv_add(result, dict_eval(PIPE_DICT_TABLE, *cpp, NO), ARGV_END); + } + } + } + argv_terminate(result); + vstring_free(buf); + return (result); +} + +/* get_service_params - get service-name dependent config information */ + +static void get_service_params(PIPE_PARAMS *config, char *service) +{ + const char *myname = "get_service_params"; + + /* + * Figure out the command time limit for this transport. + */ + config->time_limit = + get_mail_conf_time2(service, _MAXTIME, var_command_maxtime, 's', 1, 0); + + /* + * Give the poor tester a clue of what is going on. + */ + if (msg_verbose) + msg_info("%s: time_limit %d", myname, config->time_limit); +} + +/* get_service_attr - get command-line attributes */ + +static void get_service_attr(PIPE_ATTR *attr, char **argv) +{ + const char *myname = "get_service_attr"; + struct passwd *pwd; + struct group *grp; + char *user; /* user name */ + char *group; /* group name */ + char *size; /* max message size */ + char *cp; + + /* + * Initialize. + */ + user = 0; + group = 0; + attr->command = 0; + attr->flags = 0; + attr->exec_dir = 0; + attr->chroot_dir = 0; + attr->eol = vstring_strcpy(vstring_alloc(1), "\n"); + attr->null_sender = vstring_strcpy(vstring_alloc(1), MAIL_ADDR_MAIL_DAEMON); + attr->size_limit = 0; + + /* + * Iterate over the command-line attribute list. + */ + for ( /* void */ ; *argv != 0; argv++) { + + /* + * flags=stuff + */ + if (strncasecmp("flags=", *argv, sizeof("flags=") - 1) == 0) { + for (cp = *argv + sizeof("flags=") - 1; *cp; cp++) { + switch (*cp) { + case 'B': + attr->flags |= MAIL_COPY_BLANK; + break; + case 'D': + attr->flags |= MAIL_COPY_DELIVERED; + break; + case 'F': + attr->flags |= MAIL_COPY_FROM; + break; + case 'O': + attr->flags |= MAIL_COPY_ORIG_RCPT; + break; + case 'R': + attr->flags |= MAIL_COPY_RETURN_PATH; + break; + case 'X': + attr->flags |= PIPE_OPT_FINAL_DELIVERY; + break; + case '.': + attr->flags |= MAIL_COPY_DOT; + break; + case '>': + attr->flags |= MAIL_COPY_QUOTE; + break; + case 'h': + attr->flags |= PIPE_OPT_FOLD_HOST; + break; + case 'q': + attr->flags |= PIPE_OPT_QUOTE_LOCAL; + break; + case 'u': + attr->flags |= PIPE_OPT_FOLD_USER; + break; + default: + msg_fatal("unknown flag: %c (ignored)", *cp); + break; + } + } + } + + /* + * user=username[:groupname] + */ + else if (strncasecmp("user=", *argv, sizeof("user=") - 1) == 0) { + user = *argv + sizeof("user=") - 1; + if ((group = split_at(user, ':')) != 0) /* XXX clobbers argv */ + if (*group == 0) + group = 0; + if ((pwd = getpwnam(user)) == 0) + msg_fatal("%s: unknown username: %s", myname, user); + attr->uid = pwd->pw_uid; + if (group != 0) { + if ((grp = getgrnam(group)) == 0) + msg_fatal("%s: unknown group: %s", myname, group); + attr->gid = grp->gr_gid; + } else { + attr->gid = pwd->pw_gid; + } + } + + /* + * directory=string + */ + else if (strncasecmp("directory=", *argv, sizeof("directory=") - 1) == 0) { + attr->exec_dir = mystrdup(*argv + sizeof("directory=") - 1); + } + + /* + * chroot=string + */ + else if (strncasecmp("chroot=", *argv, sizeof("chroot=") - 1) == 0) { + attr->chroot_dir = mystrdup(*argv + sizeof("chroot=") - 1); + } + + /* + * eol=string + */ + else if (strncasecmp("eol=", *argv, sizeof("eol=") - 1) == 0) { + unescape(attr->eol, *argv + sizeof("eol=") - 1); + } + + /* + * null_sender=string + */ + else if (strncasecmp("null_sender=", *argv, sizeof("null_sender=") - 1) == 0) { + vstring_strcpy(attr->null_sender, *argv + sizeof("null_sender=") - 1); + } + + /* + * size=max_message_size (in bytes) + */ + else if (strncasecmp("size=", *argv, sizeof("size=") - 1) == 0) { + size = *argv + sizeof("size=") - 1; + if ((attr->size_limit = off_cvt_string(size)) < 0) + msg_fatal("%s: bad size= value: %s", myname, size); + } + + /* + * argv=command... + */ + else if (strncasecmp("argv=", *argv, sizeof("argv=") - 1) == 0) { + *argv += sizeof("argv=") - 1; /* XXX clobbers argv */ + attr->command = argv; + break; + } + + /* + * Bad. + */ + else + msg_fatal("unknown attribute name: %s", *argv); + } + + /* + * Sanity checks. Verify that every member has an acceptable value. + */ + if (user == 0) + msg_fatal("missing user= command-line attribute"); + if (attr->command == 0) + msg_fatal("missing argv= command-line attribute"); + if (attr->uid == 0) + msg_fatal("user= command-line attribute specifies root privileges"); + if (attr->uid == var_owner_uid) + msg_fatal("user= command-line attribute specifies mail system owner %s", + var_mail_owner); + if (attr->gid == 0) + msg_fatal("user= command-line attribute specifies privileged group id 0"); + if (attr->gid == var_owner_gid) + msg_fatal("user= command-line attribute specifies mail system owner %s group id %ld", + var_mail_owner, (long) attr->gid); + if (attr->gid == var_sgid_gid) + msg_fatal("user= command-line attribute specifies mail system %s group id %ld", + var_sgid_group, (long) attr->gid); + + /* + * Give the poor tester a clue of what is going on. + */ + if (msg_verbose) + msg_info("%s: uid %ld, gid %ld, flags %d, size %ld", + myname, (long) attr->uid, (long) attr->gid, + attr->flags, (long) attr->size_limit); +} + +/* eval_command_status - do something with command completion status */ + +static int eval_command_status(int command_status, char *service, + DELIVER_REQUEST *request, PIPE_ATTR *attr, + DSN_BUF *why) +{ + RECIPIENT *rcpt; + int status; + int result = 0; + int n; + char *saved_text; + + /* + * Depending on the result, bounce or defer the message, and mark the + * recipient as done where appropriate. + */ + switch (command_status) { + case PIPE_STAT_OK: + /* Save the command output before dsb_update() clobbers it. */ + vstring_truncate(why->reason, trimblanks(STR(why->reason), + VSTRING_LEN(why->reason)) - STR(why->reason)); + if (VSTRING_LEN(why->reason) > 0) { + VSTRING_TERMINATE(why->reason); + saved_text = + vstring_export(vstring_sprintf( + vstring_alloc(VSTRING_LEN(why->reason)), + " (%.100s)", STR(why->reason))); + } else + saved_text = mystrdup(""); /* uses shared R/O storage */ + dsb_update(why, "2.0.0", (attr->flags & PIPE_OPT_FINAL_DELIVERY) ? + "delivered" : "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY, + "delivered via %s service%s", service, saved_text); + myfree(saved_text); + (void) DSN_FROM_DSN_BUF(why); + for (n = 0; n < request->rcpt_list.len; n++) { + rcpt = request->rcpt_list.info + n; + status = sent(DEL_REQ_TRACE_FLAGS(request->flags), + request->queue_id, &request->msg_stats, rcpt, + service, &why->dsn); + if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS)) + deliver_completed(request->fp, rcpt->offset); + result |= status; + } + break; + case PIPE_STAT_BOUNCE: + case PIPE_STAT_DEFER: + (void) DSN_FROM_DSN_BUF(why); + for (n = 0; n < request->rcpt_list.len; n++) { + rcpt = request->rcpt_list.info + n; + /* XXX Maybe encapsulate this with ndr_append(). */ + status = (STR(why->status)[0] != '4' ? + bounce_append : defer_append) + (DEL_REQ_TRACE_FLAGS(request->flags), + request->queue_id, + &request->msg_stats, rcpt, + service, &why->dsn); + if (status == 0) + deliver_completed(request->fp, rcpt->offset); + result |= status; + } + break; + case PIPE_STAT_CORRUPT: + /* XXX DSN should we send something? */ + result |= DEL_STAT_DEFER; + break; + default: + msg_panic("eval_command_status: bad status %d", command_status); + /* NOTREACHED */ + } + + return (result); +} + +/* deliver_message - deliver message with extreme prejudice */ + +static int deliver_message(DELIVER_REQUEST *request, char *service, char **argv) +{ + const char *myname = "deliver_message"; + static PIPE_PARAMS conf; + static PIPE_ATTR attr; + RECIPIENT_LIST *rcpt_list = &request->rcpt_list; + DSN_BUF *why = dsb_create(); + VSTRING *buf; + ARGV *expanded_argv = 0; + int deliver_status; + int command_status; + ARGV *export_env; + const char *sender; + +#define DELIVER_MSG_CLEANUP() { \ + dsb_free(why); \ + if (expanded_argv) argv_free(expanded_argv); \ + } + + if (msg_verbose) + msg_info("%s: from <%s>", myname, request->sender); + + /* + * Sanity checks. The get_service_params() and get_service_attr() + * routines also do some sanity checks. Look up service attributes and + * config information only once. This is safe since the information comes + * from a trusted source, not from the delivery request. + */ + if (request->nexthop[0] == 0) + msg_fatal("empty nexthop hostname"); + if (rcpt_list->len <= 0) + msg_fatal("recipient count: %d", rcpt_list->len); + if (attr.command == 0) { + get_service_params(&conf, service); + get_service_attr(&attr, argv); + } + + /* + * The D flag cannot be specified for multi-recipient deliveries. + */ + if ((attr.flags & MAIL_COPY_DELIVERED) && (rcpt_list->len > 1)) { + dsb_simple(why, "4.3.5", "mail system configuration error"); + deliver_status = eval_command_status(PIPE_STAT_DEFER, service, + request, &attr, why); + msg_warn("pipe flag `D' requires %s_destination_recipient_limit = 1", + service); + DELIVER_MSG_CLEANUP(); + return (deliver_status); + } + + /* + * The O flag cannot be specified for multi-recipient deliveries. + */ + if ((attr.flags & MAIL_COPY_ORIG_RCPT) && (rcpt_list->len > 1)) { + dsb_simple(why, "4.3.5", "mail system configuration error"); + deliver_status = eval_command_status(PIPE_STAT_DEFER, service, + request, &attr, why); + msg_warn("pipe flag `O' requires %s_destination_recipient_limit = 1", + service); + DELIVER_MSG_CLEANUP(); + return (deliver_status); + } + + /* + * Check that this agent accepts messages this large. + */ + if (attr.size_limit != 0 && request->data_size > attr.size_limit) { + if (msg_verbose) + msg_info("%s: too big: size_limit = %ld, request->data_size = %ld", + myname, (long) attr.size_limit, request->data_size); + dsb_simple(why, "5.2.3", "message too large"); + deliver_status = eval_command_status(PIPE_STAT_BOUNCE, service, + request, &attr, why); + DELIVER_MSG_CLEANUP(); + return (deliver_status); + } + + /* + * Don't deliver a trace-only request. + */ + if (DEL_REQ_TRACE_ONLY(request->flags)) { + RECIPIENT *rcpt; + int status; + int n; + + deliver_status = 0; + dsb_simple(why, "2.0.0", "delivers to command: %s", attr.command[0]); + (void) DSN_FROM_DSN_BUF(why); + for (n = 0; n < request->rcpt_list.len; n++) { + rcpt = request->rcpt_list.info + n; + status = sent(DEL_REQ_TRACE_FLAGS(request->flags), + request->queue_id, &request->msg_stats, + rcpt, service, &why->dsn); + if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS)) + deliver_completed(request->fp, rcpt->offset); + deliver_status |= status; + } + DELIVER_MSG_CLEANUP(); + return (deliver_status); + } + + /* + * Report mail delivery loops. By definition, this requires + * single-recipient delivery. Don't silently lose recipients. + */ + if (attr.flags & MAIL_COPY_DELIVERED) { + DELIVERED_HDR_INFO *info; + RECIPIENT *rcpt; + int loop_found; + + if (request->rcpt_list.len > 1) + msg_panic("%s: delivered-to enabled with multi-recipient request", + myname); + info = delivered_hdr_init(request->fp, request->data_offset, + FOLD_ADDR_ALL); + rcpt = request->rcpt_list.info; + loop_found = delivered_hdr_find(info, rcpt->address); + delivered_hdr_free(info); + if (loop_found) { + dsb_simple(why, "5.4.6", "mail forwarding loop for %s", + rcpt->address); + deliver_status = eval_command_status(PIPE_STAT_BOUNCE, service, + request, &attr, why); + DELIVER_MSG_CLEANUP(); + return (deliver_status); + } + } + + /* + * Deliver. Set the nexthop and sender variables, and expand the command + * argument vector. Recipients will be expanded on the fly. XXX Rewrite + * envelope and header addresses according to transport-specific + * rewriting rules. + */ + if (vstream_fseek(request->fp, request->data_offset, SEEK_SET) < 0) + msg_fatal("seek queue file %s: %m", VSTREAM_PATH(request->fp)); + + /* + * A non-empty null sender replacement is subject to the 'q' flag. + */ + buf = vstring_alloc(10); + sender = *request->sender ? request->sender : STR(attr.null_sender); + if (*sender && (attr.flags & PIPE_OPT_QUOTE_LOCAL)) { + quote_822_local(buf, sender); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, STR(buf)); + } else + dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, sender); + if (attr.flags & PIPE_OPT_FOLD_HOST) { + casefold(buf, request->nexthop); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, STR(buf)); + } else + dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, request->nexthop); + vstring_sprintf(buf, "%ld", (long) request->data_size); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_SIZE, STR(buf)); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_ADDR, + request->client_addr); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_HELO, + request->client_helo); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_NAME, + request->client_name); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_PORT, + request->client_port); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_PROTO, + request->client_proto); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_METHOD, + request->sasl_method); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_USERNAME, + request->sasl_username); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_SENDER, + request->sasl_sender); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_QUEUE_ID, + request->queue_id); + vstring_free(buf); + + if ((expanded_argv = expand_argv(service, attr.command, + rcpt_list, attr.flags)) == 0) { + dsb_simple(why, "4.3.5", "mail system configuration error"); + deliver_status = eval_command_status(PIPE_STAT_DEFER, service, + request, &attr, why); + DELIVER_MSG_CLEANUP(); + return (deliver_status); + } + export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ); + + command_status = pipe_command(request->fp, why, + CA_PIPE_CMD_UID(attr.uid), + CA_PIPE_CMD_GID(attr.gid), + CA_PIPE_CMD_SENDER(sender), + CA_PIPE_CMD_COPY_FLAGS(attr.flags), + CA_PIPE_CMD_ARGV(expanded_argv->argv), + CA_PIPE_CMD_TIME_LIMIT(conf.time_limit), + CA_PIPE_CMD_EOL(STR(attr.eol)), + CA_PIPE_CMD_EXPORT(export_env->argv), + CA_PIPE_CMD_CWD(attr.exec_dir), + CA_PIPE_CMD_CHROOT(attr.chroot_dir), + CA_PIPE_CMD_ORIG_RCPT(rcpt_list->info[0].orig_addr), + CA_PIPE_CMD_DELIVERED(rcpt_list->info[0].address), + CA_PIPE_CMD_END); + argv_free(export_env); + + deliver_status = eval_command_status(command_status, service, request, + &attr, why); + + /* + * Clean up. + */ + DELIVER_MSG_CLEANUP(); + + return (deliver_status); +} + +/* pipe_service - perform service for client */ + +static void pipe_service(VSTREAM *client_stream, char *service, char **argv) +{ + DELIVER_REQUEST *request; + int status; + + /* + * This routine runs whenever a client connects to the UNIX-domain socket + * dedicated to delivery via external command. What we see below is a + * little protocol to (1) tell the queue manager that we are ready, (2) + * read a request from the queue manager, and (3) report the completion + * status of that request. All connection-management stuff is handled by + * the common code in single_server.c. + */ + if ((request = deliver_request_read(client_stream)) != 0) { + status = deliver_message(request, service, argv); + deliver_request_done(client_stream, request, status); + } +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +/* drop_privileges - drop privileges most of the time */ + +static void drop_privileges(char *unused_name, char **unused_argv) +{ + set_eugid(var_owner_uid, var_owner_gid); +} + +/* pre_init - initialize */ + +static void pre_init(char *unused_name, char **unused_argv) +{ + flush_init(); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the single-threaded skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_TIME_TABLE time_table[] = { + VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0, + 0, + }; + static const CONFIG_STR_TABLE str_table[] = { + VAR_PIPE_DSN_FILTER, DEF_PIPE_DSN_FILTER, &var_pipe_dsn_filter, 0, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + single_server_main(argc, argv, pipe_service, + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_PRE_INIT(pre_init), + CA_MAIL_SERVER_POST_INIT(drop_privileges), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_PRIVILEGED, + CA_MAIL_SERVER_BOUNCE_INIT(VAR_PIPE_DSN_FILTER, + &var_pipe_dsn_filter), + 0); +} |