diff --git a/postfix/.indent.pro b/postfix/.indent.pro index 3dcb109b1..23f1f46ce 100644 --- a/postfix/.indent.pro +++ b/postfix/.indent.pro @@ -1,3 +1,4 @@ +-TABOUNCE -TALIAS_TOKEN -TARGV -TBH_TABLE diff --git a/postfix/COMPATIBILITY b/postfix/COMPATIBILITY index bb754a363..76803407a 100644 --- a/postfix/COMPATIBILITY +++ b/postfix/COMPATIBILITY @@ -25,7 +25,7 @@ lmtp support yes (client) m4 config no mail to command yes (configurable for .forward, aliases, :include:) mail to file yes (configurable for .forward, aliases, :include:) -maildir yes (with procmail) +maildir yes mailertable yes (it's called transport) mailq yes majordomo yes (edit approve script to delete /delivered-to/i) @@ -38,6 +38,7 @@ nis tables yes nis+ tables not yet pipeline option yes (server and client) pop/imap yes (with third-party daemons that use /var[/spool]/mail) +qmqp server yes (with verp support) rbl support yes return-receipt: not yet sasl support yes (compile time option) @@ -55,5 +56,6 @@ user+extension yes (also: .forward+extension) user-extension yes (also: .forward-extension) user.lock yes (runtime configurable) uucp support yes (sends user@domain recipients) +verp support yes (delimiters are configurable) virtual domains yes year 2000 safe yes diff --git a/postfix/HISTORY b/postfix/HISTORY index 17a0dfc47..1b82ef459 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -5292,13 +5292,12 @@ Apologies for any names omitted. Cleanup: the virtual delivery agent was poorly integrated so that the SMTP server and queue manager did not reject - mail for unknown users. Files: smtpd/smtpd_check.c, - *qmgr/qmgr_message.c. + mail for unknown users. Files: smtpd/smtpd_check.c. 20010705 - Feature: QMQP server for compatibility with the ezmlm list - manager. Files: util/netstring.[hc], qmqpd/qmqpd*.c. + Feature: QMQP server, compatible with qmail and the ezmlm + list manager. Files: util/netstring.[hc], qmqpd/qmqpd*.c. 20010706 @@ -5309,3 +5308,17 @@ Apologies for any names omitted. Bugfix: with disable_dns=yes, the SMTP client treated all host lookup errors as permanent. File: smtp/smtp_addr.c. + +20010709 + + Feature: VERP support, based on a patch by Peng Yong, and + with the missing parts filled in so that the Postfix bounce + daemon can send one VERP bounce per undeliverable recipient. + Files: , sendmail/sendmail.c, smtpd/smtpd.c, qmgr/qmgr_deliver.c, + bounce/bounce_notify_verp.c, qmqpd/qmqpd.c, plus a couple + support routines in the global library. + + Cleanup: with recipient_delimiter=+ (or any character other + than -) Postfix will now recognize address extensions even + with owner-foo+extension addresses. This is necessary to + make VERP work for mailing lists. diff --git a/postfix/INSTALL b/postfix/INSTALL index da6526c74..6e74ebcbf 100644 --- a/postfix/INSTALL +++ b/postfix/INSTALL @@ -145,7 +145,7 @@ expect to run more than 1000 delivery processes, you may need to override the definition of the FD_SETSIZE macro to make select() work correctly: - % make makefiles CCARGS=-FD_SETSIZE=2048 + % make makefiles CCARGS=-DFD_SETSIZE=2048 In any case, if the command diff --git a/postfix/README_QMQP b/postfix/QMQP_README similarity index 78% rename from postfix/README_QMQP rename to postfix/QMQP_README index 666f79e41..8f9ed0776 100644 --- a/postfix/README_QMQP +++ b/postfix/QMQP_README @@ -6,8 +6,8 @@ that Postfix can be used as a backend for the Ezmlm-idx mailing list manager. This support includes qmqp-source and qmqp-sink programs for protocol stress testing. -Turning on the QMQP service -=========================== +Turning on the Postfix QMQP service +=================================== To enable QMQP server support on an existing Postfix system you have to add the following line to /etc/postfix/master.cf: @@ -15,8 +15,8 @@ have to add the following line to /etc/postfix/master.cf: 628 inet n - n - - qmqpd -QMQP server access control -========================== +Postfix QMQP server access control +================================== By default, the QMQP server does not accept mail from any client. This is because the QMQP server relays mail to any destination @@ -37,3 +37,9 @@ instead. Patterns are separated by whitespace and/or commas. In order to reverse the result, precede a non-file name pattern with an exclamation point (!). + +Setting up Ezmlm-idx to use Postfix QMQP support +================================================ + +You need to list the Postfix IP address in a suitable configuration +file. See the ezmlm-idx documentation for details. diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index 71b51f4ac..48abe2be6 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -1,17 +1,33 @@ -Incompatible changes with snapshot-20010707 +Incompatible changes with snapshot-20010709 =========================================== -The SMTP client by default breaks lines > 2048 characters, in order -to avoid problems with mail delivery to fragile SMTP server software. -To get the old behavior, specify "smtp_break_lines = no" in the -Postfix main.cf file. +This release introduces a new queue file record type that is used +only for messages that actually use VERP (variable envelope return +path) support. With this sole exception, the queue file format is +entirely backwards compatible with previous Postfix releases. -Major changes with snapshot-20010707 +The SMTP client now by default breaks lines > 2048 characters, to +avoid mail delivery problems with fragile SMTP server software. +To get the old behavior back, specify "smtp_break_lines = no" in +the Postfix main.cf file. + +With recipient_delimiter=+ (or any character other than -) Postfix +will now recognize address extensions even with owner-foo+extension +addresses. This change was necessary to make VERP useful for mailing +list bounce processing. + +Major changes with snapshot-20010709 ==================================== QMQP server support, so that Postfix can be used as a backend mailer -for the Ezmlm-idx mailing list manager. The service is disabled by -default. To enable, follow instructions in the README_QMQP file. +for the ezmlm-idx mailing list manager. You still need qmail to +drive ezmlm and to process mailing list bounces. The QMQP service +is disabled by default. To enable, follow the instructions in the +QMQP_README file. + +VERP (variable envelope return path) support. This is enabled by +default. See the VERP_README file for instructions. These instructions +need more examples for how to process bounces automatically. You can now reject unknown virtual(8) recipients at the SMTP port by specifying a "domain.name whatever" entry in the tables specified diff --git a/postfix/VERP_README b/postfix/VERP_README new file mode 100644 index 000000000..d8c9e2f46 --- /dev/null +++ b/postfix/VERP_README @@ -0,0 +1,96 @@ +Postfix VERP support +==================== + +Postfix supports variable envelope return path addresses, which +means that each recipient receives a customized copy of the message, +with the recipient address encoded in the envelope sender address. +This concept was popularized by the qmail MTA and by the ezmlm +mailing list manager. + +When VERP style delivery is requested, Postfix delivers mail with +sender address prefix@origin for a recipient user@domain, with a +sender address that encodes the recipient as follows: + + prefix+user=domain@origin + +so that undeliverable mail reveals what address was undeliverable. + +The + and = are the default VERP delimiters. You can specify non- +default delimiters in main.cf with the default_verp_delimiters +configuration parameter (default value: +=). Specify two characters; +the first delimiter should match the $recipient_delimiter setting. + +Using VERP with majordomo etc. mailing lists +============================================ + +In order to make VERP useful with majordomo etc. mailing lists, +you would configure the list manager to submit mail as: + + sendmail -V -f owner-listname other-arguments... + +This text assumes that you have set up an owner-listname alias that +routes undeliverable mail to a real person: + + /etc/aliases: + owner-listname: yourname+listname + +In order to process bounces we are going to make extensive use of +address extension tricks. + +You need to tell Postfix that + is the separator between an address +and its optional address extension, that address extensions are +appended to .forward file names, and that address extensions are +to be discarded when doing alias expansions: + + /etc/postfix/main.cf: + recipient_delimiter = + + forward_path = $home/.forward${recipient_delimiter}${extension},$home/.forward + propagate_unmatched_extensions = canonical, virtual + +(the last two parameter settings are default settings). + +You need to set up a file named .forward+listname with the commands +that process all the mail that is sent to the owner-listname address: + + ~/.forward+listname: + "|/some/where/command ..." + +With this set up, undeliverable mail for user@domain will be returned +to the following address: + + owner-listname+user=domain@your.domain + +which is processed by the command in your .forward+listname file. + +It is left as an exercise for the reader to parse the To: header +line and to pull out the user=domain part from the recipient address. + +VERP support in the Postfix SMTP server +======================================= + +The Postfix SMTP server has a new command XVERP to enable VERP +style delivery. The syntax allows two forms: + + MAIL FROM: XVERP + MAIL FROM: XVERP=xy + +where x and y are the VERP delimiters. When no VERP delimiters +are specified, Postfix uses the two characters specified with the +default_verp_delimiters configuration parameter. + +VERP support in the Postfix sendmail command +============================================ + +The Postfix sendmail command has a -V flag to request VERP style +delivery. It is not possible to override the default VERP delimiters. + +VERP support in the Postfix QMQP server +======================================= + +When the Postfix QMQP server receives mail with a an envelope sender +address of the form: + + prefix-@origin-@[] + +Postfix generates VERP sender addresses using prefix@domain as the +original sender address, and using "-=" as the VERP delimiters. diff --git a/postfix/html/sendmail.1.html b/postfix/html/sendmail.1.html index 1d414d2df..1c6848aeb 100644 --- a/postfix/html/sendmail.1.html +++ b/postfix/html/sendmail.1.html @@ -94,33 +94,38 @@ SENDMAIL(1) SENDMAIL(1) -U (ignored) Initial user submission. - -bd Go into daemon mode. This mode of operation is + -V Variable Envelope Return Path. Given an envelope + sender address prefix-@origin, each recipient + user@domain receives mail with a personalized enve- + lope sender address prefix-user=domain@origin. + + -bd Go into daemon mode. This mode of operation is implemented by executing the postfix start command. - -bi Initialize alias database. See the newaliases com- + -bi Initialize alias database. See the newaliases com- mand above. - -bm Read mail from standard input and arrange for + -bm Read mail from standard input and arrange for delivery. This is the default mode of operation. -bp List the mail queue. See the mailq command above. - -bs Stand-alone SMTP server mode. Read SMTP commands - from standard input, and write responses to stan- + -bs Stand-alone SMTP server mode. Read SMTP commands + from standard input, and write responses to stan- dard output. This mode of operation is implemented by running the smtpd(8) daemon. -f sender Set the envelope sender address. This is the address where delivery problems are sent to, unless - the message contains an Errors-To: message header. + the message contains an Errors-To: message header. -h hop_count (ignored) - Hop count limit. Use the hopcount_limit configura- + Hop count limit. Use the hopcount_limit configura- tion parameter instead. - -i When reading a message from standard input, don't - treat a line with only a . character as the end of + -i When reading a message from standard input, don't + treat a line with only a . character as the end of input. -m (ignored) @@ -130,68 +135,68 @@ SENDMAIL(1) SENDMAIL(1) Backwards compatibility. -oAalias_database - Non-default alias database. Specify pathname or + Non-default alias database. Specify pathname or type:pathname. See postalias(1) for details. -o7 (ignored) -o8 (ignored) - The message body type. Currently, Postfix imple- + The message body type. Currently, Postfix imple- ments just-send-eight. - -oi When reading a message from standard input, don't - treat a line with only a . character as the end of + -oi When reading a message from standard input, don't + treat a line with only a . character as the end of input. -om (ignored) - The sender is never eliminated from alias etc. + The sender is never eliminated from alias etc. expansions. -o x value (ignored) - Set option x to value. Use the equivalent configu- + Set option x to value. Use the equivalent configu- ration parameter in main.cf instead. -r sender Set the envelope sender address. This is the address where delivery problems are sent to, unless - the message contains an Errors-To: message header. + the message contains an Errors-To: message header. - -q Attempt to deliver all queued mail. This is imple- + -q Attempt to deliver all queued mail. This is imple- mented by kicking the qmgr(8) daemon. -qinterval (ignored) - The interval between queue runs. Use the + The interval between queue runs. Use the queue_run_delay configuration parameter instead. -qRsite - Schedule immediate delivery of all mail that is - queued for the named site. Depending on the desti- - nation, this uses "fast flush" service, or it has - the same effect as sendmail -q. This is imple- + Schedule immediate delivery of all mail that is + queued for the named site. Depending on the desti- + nation, this uses "fast flush" service, or it has + the same effect as sendmail -q. This is imple- mented by connecting to the local SMTP server. See smtpd(8) for more information about the "fast flush" service. -qSsite - This command is not implemented. Use the slower + This command is not implemented. Use the slower sendmail -q command instead. - -t Extract recipients from message headers. This - requires that no recipients be specified on the + -t Extract recipients from message headers. This + requires that no recipients be specified on the command line. -v Enable verbose logging for debugging purposes. Mul- - tiple -v options make the software increasingly + tiple -v options make the software increasingly verbose. SECURITY - By design, this program is not set-user (or group) id. - However, it must handle data from untrusted users or - untrusted machines. Thus, the usual precautions need to + By design, this program is not set-user (or group) id. + However, it must handle data from untrusted users or + untrusted machines. Thus, the usual precautions need to be taken against malicious inputs. DIAGNOSTICS - Problems are logged to syslogd(8) and to the standard + Problems are logged to syslogd(8) and to the standard error stream. ENVIRONMENT @@ -203,7 +208,7 @@ SENDMAIL(1) SENDMAIL(1) MAIL_DEBUG Enable debugging with an external command, as spec- - ified with the debugger_command configuration + ified with the debugger_command configuration parameter. FILES @@ -211,13 +216,13 @@ SENDMAIL(1) SENDMAIL(1) /etc/postfix, configuration files CONFIGURATION PARAMETERS - See the Postfix main.cf file for syntax details and for - default values. Use the postfix reload command after a + See the Postfix main.cf file for syntax details and for + default values. Use the postfix reload command after a configuration change. alias_database - Default alias database(s) for newaliases. The - default value for this parameter is system-spe- + Default alias database(s) for newaliases. The + default value for this parameter is system-spe- cific. bounce_size_limit @@ -233,55 +238,55 @@ SENDMAIL(1) SENDMAIL(1) initialized. debug_peer_level - Increment in verbose logging level when a remote + Increment in verbose logging level when a remote host matches a pattern in the debug_peer_list parameter. debug_peer_list - List of domain or network patterns. When a remote - host matches a pattern, increase the verbose log- - ging level by the amount specified in the + List of domain or network patterns. When a remote + host matches a pattern, increase the verbose log- + ging level by the amount specified in the debug_peer_level parameter. fast_flush_domains List of domains that will receive "fast flush" ser- - vice (default: all domains that this system is - willing to relay mail to). This greatly improves - the performance of the SMTP ETRN request, and of - the sendmail -qR command. For domains not in the + vice (default: all domains that this system is + willing to relay mail to). This greatly improves + the performance of the SMTP ETRN request, and of + the sendmail -qR command. For domains not in the list, Postfix simply attempts to deliver all queued mail. fork_attempts - Number of attempts to fork() a process before giv- + Number of attempts to fork() a process before giv- ing up. fork_delay - Delay in seconds between successive fork() + Delay in seconds between successive fork() attempts. hopcount_limit Limit the number of Received: message headers. mail_owner - The owner of the mail queue and of most Postfix + The owner of the mail queue and of most Postfix processes. command_directory - Directory with Postfix support commands (default: + Directory with Postfix support commands (default: $program_directory). daemon_directory - Directory with Postfix daemon programs (default: + Directory with Postfix daemon programs (default: $program_directory). queue_directory - Top-level directory of the Postfix queue. This is + Top-level directory of the Postfix queue. This is also the root directory of Postfix daemons that run chrooted. queue_run_delay - The time between successive scans of the deferred + The time between successive scans of the deferred queue. SEE ALSO @@ -297,7 +302,7 @@ SENDMAIL(1) SENDMAIL(1) syslogd(8) system logging LICENSE - The Secure Mailer license must be distributed with this + The Secure Mailer license must be distributed with this software. AUTHOR(S) diff --git a/postfix/html/smtpd.8.html b/postfix/html/smtpd.8.html index 2e6c95d21..23ec5069f 100644 --- a/postfix/html/smtpd.8.html +++ b/postfix/html/smtpd.8.html @@ -35,8 +35,8 @@ SMTPD(8) SMTPD(8) STANDARDS RFC 821 (SMTP protocol) RFC 1123 (Host requirements) - RFC 1651 (SMTP service extensions) RFC 1652 (8bit-MIME transport) + RFC 1869 (SMTP service extensions) RFC 1854 (SMTP Pipelining) RFC 1870 (Message Size Declaration) RFC 1985 (ETRN command) diff --git a/postfix/man/man1/sendmail.1 b/postfix/man/man1/sendmail.1 index b7dade23f..a3a01dda5 100644 --- a/postfix/man/man1/sendmail.1 +++ b/postfix/man/man1/sendmail.1 @@ -82,6 +82,11 @@ Log mailer traffic. Use the \fBdebug_peer_list\fR and \fBdebug_peer_level\fR configuration parameters instead. .IP "\fB-U\fR (ignored)" Initial user submission. +.IP \fB-V\fR +Variable Envelope Return Path. Given an envelope sender address +\fIprefix\fR-@\fIorigin\fR, each recipient \fIuser@domain\fR +receives mail with a personalized envelope sender address +\fIprefix\fB-\fIuser=domain\fR@\fIorigin\fR. .IP \fB-bd\fR Go into daemon mode. This mode of operation is implemented by executing the \fBpostfix start\fR command. diff --git a/postfix/man/man8/smtpd.8 b/postfix/man/man8/smtpd.8 index 9c3b9236b..2a979fee3 100644 --- a/postfix/man/man8/smtpd.8 +++ b/postfix/man/man8/smtpd.8 @@ -42,8 +42,8 @@ run chrooted at fixed low privilege. .nf RFC 821 (SMTP protocol) RFC 1123 (Host requirements) -RFC 1651 (SMTP service extensions) RFC 1652 (8bit-MIME transport) +RFC 1869 (SMTP service extensions) RFC 1854 (SMTP Pipelining) RFC 1870 (Message Size Declaration) RFC 1985 (ETRN command) diff --git a/postfix/src/bounce/Makefile.in b/postfix/src/bounce/Makefile.in index 4172afd76..cad8498f0 100644 --- a/postfix/src/bounce/Makefile.in +++ b/postfix/src/bounce/Makefile.in @@ -1,8 +1,8 @@ SHELL = /bin/sh SRCS = bounce.c bounce_append_service.c bounce_notify_service.c \ - bounce_cleanup.c bounce_notify_util.c + bounce_cleanup.c bounce_notify_util.c bounce_notify_verp.c OBJS = bounce.o bounce_append_service.o bounce_notify_service.o \ - bounce_cleanup.o bounce_notify_util.o + bounce_cleanup.o bounce_notify_util.o bounce_notify_verp.o HDRS = TESTSRC = WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \ @@ -69,6 +69,7 @@ bounce.o: ../../include/mail_queue.h bounce.o: ../../include/mail_params.h bounce.o: ../../include/mail_conf.h bounce.o: ../../include/bounce.h +bounce.o: ../../include/mail_addr.h bounce.o: ../../include/mail_server.h bounce.o: bounce_service.h bounce.o: ../../include/bounce_log.h @@ -133,3 +134,18 @@ bounce_notify_util.o: ../../include/name_mask.h bounce_notify_util.o: ../../include/bounce_log.h bounce_notify_util.o: ../../include/mail_date.h bounce_notify_util.o: bounce_service.h +bounce_notify_verp.o: bounce_notify_verp.c +bounce_notify_verp.o: ../../include/sys_defs.h +bounce_notify_verp.o: ../../include/msg.h +bounce_notify_verp.o: ../../include/vstream.h +bounce_notify_verp.o: ../../include/vbuf.h +bounce_notify_verp.o: ../../include/name_mask.h +bounce_notify_verp.o: ../../include/mail_params.h +bounce_notify_verp.o: ../../include/mail_queue.h +bounce_notify_verp.o: ../../include/vstring.h +bounce_notify_verp.o: ../../include/post_mail.h +bounce_notify_verp.o: ../../include/cleanup_user.h +bounce_notify_verp.o: ../../include/mail_addr.h +bounce_notify_verp.o: ../../include/mail_error.h +bounce_notify_verp.o: bounce_service.h +bounce_notify_verp.o: ../../include/bounce_log.h diff --git a/postfix/src/bounce/bounce.c b/postfix/src/bounce/bounce.c index eeedb7b14..75a62032e 100644 --- a/postfix/src/bounce/bounce.c +++ b/postfix/src/bounce/bounce.c @@ -80,6 +80,11 @@ /* System library. */ #include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif /* Utility library. */ @@ -95,6 +100,7 @@ #include #include #include +#include /* Single-threaded server skeleton. */ @@ -122,6 +128,7 @@ static VSTRING *queue_id; static VSTRING *queue_name; static VSTRING *recipient; static VSTRING *sender; +static VSTRING *verp_delims; static VSTRING *why; #define STR vstring_str @@ -199,7 +206,62 @@ static int bounce_notify_proto(char *service_name, VSTREAM *client, int flush) * Execute the request. */ return (bounce_notify_service(service_name, STR(queue_name), - STR(queue_id), STR(sender), flush)); + STR(queue_id), STR(sender), flush)); +} + +/* bounce_verp_proto - bounce_notify server protocol, VERP style */ + +static int bounce_verp_proto(char *service_name, VSTREAM *client, int flush) +{ + char *myname="bounce_verp_proto"; + int flags; + + /* + * Read and validate the client request. + */ + if (mail_command_read(client, "%d %s %s %s %s", + &flags, queue_name, queue_id, + sender, verp_delims) != 5) { + msg_warn("malformed request"); + return (-1); + } + if (mail_queue_name_ok(STR(queue_name)) == 0) { + msg_warn("malformed queue name: %s", printable(STR(queue_name), '?')); + return (-1); + } + if (mail_queue_id_ok(STR(queue_id)) == 0) { + msg_warn("malformed queue id: %s", printable(STR(queue_id), '?')); + return (-1); + } + if (strlen(STR(verp_delims)) != 2) { + msg_warn("malformed verp delimiter string: %s", + printable(STR(verp_delims), '?')); + return (-1); + } + if (msg_verbose) + msg_info("%s: service=%s queue=%s id=%s sender=%s delim=%s", + myname, service_name, STR(queue_name), STR(queue_id), + STR(sender), STR(verp_delims)); + + /* + * On request by the client, set up a trap to delete the log file in case + * of errors. + */ + if (flags & BOUNCE_FLAG_CLEAN) + bounce_cleanup_register(service_name, STR(queue_id)); + + /* + * Execute the request. Fall back to traditional notification if a bounce + * was returned as undeliverable, because we don't want to VERPify those. + */ + if (!*STR(sender) || !strcasecmp(STR(sender), mail_addr_double_bounce())) { + msg_warn("request to send VERP-style notification of bounced mail"); + return (bounce_notify_service(service_name, STR(queue_name), + STR(queue_id), STR(sender), flush)); + } else + return (bounce_notify_verp(service_name, STR(queue_name), + STR(queue_id), STR(sender), + STR(verp_delims), flush)); } /* bounce_service - parse bounce command type and delegate */ @@ -228,6 +290,8 @@ static void bounce_service(VSTREAM *client, char *service_name, char **argv) if (mail_scan(client, "%d", &command) != 1) { msg_warn("malformed request"); status = -1; + } else if (command == BOUNCE_CMD_VERP) { + status = bounce_verp_proto(service_name, client, REALLY_BOUNCE); } else if (command == BOUNCE_CMD_FLUSH) { status = bounce_notify_proto(service_name, client, REALLY_BOUNCE); } else if (command == BOUNCE_CMD_WARN) { @@ -271,6 +335,7 @@ static void post_jail_init(char *unused_name, char **unused_argv) queue_name = vstring_alloc(10); recipient = vstring_alloc(10); sender = vstring_alloc(10); + verp_delims = vstring_alloc(10); why = vstring_alloc(10); } diff --git a/postfix/src/bounce/bounce_notify_util.c b/postfix/src/bounce/bounce_notify_util.c index cc5e15f6b..efa68ce49 100644 --- a/postfix/src/bounce/bounce_notify_util.c +++ b/postfix/src/bounce/bounce_notify_util.c @@ -199,7 +199,7 @@ BOUNCE_INFO *bounce_mail_init(const char *service, const char *queue_name, */ if ((bounce_info->log_handle = bounce_log_open(bounce_info->service, bounce_info->queue_id, - O_RDONLY, 0)) == 0 + O_RDWR, 0)) == 0 && errno != ENOENT) msg_fatal("open %s %s: %m", bounce_info->service, bounce_info->queue_id); diff --git a/postfix/src/bounce/bounce_notify_verp.c b/postfix/src/bounce/bounce_notify_verp.c new file mode 100644 index 000000000..f1880bae0 --- /dev/null +++ b/postfix/src/bounce/bounce_notify_verp.c @@ -0,0 +1,210 @@ +/*++ +/* NAME +/* bounce_notify_verp 3 +/* SUMMARY +/* send non-delivery report to sender, server side +/* SYNOPSIS +/* #include "bounce_service.h" +/* +/* int bounce_notify_verp(service, queue_name, queue_id, sender, +/* verp_delims, flush) +/* char *queue_name; +/* char *queue_id; +/* char *sender; +/* char *verp_delims; +/* int flush; +/* DESCRIPTION +/* This module implements the server side of the bounce_notify() +/* (send bounce message) request. If flush is zero, the logfile +/* is not removed, and a warning is sent instead of a bounce. +/* The bounce recipient address is encoded in VERP format. +/* This routine must be used for single bounces only. +/* +/* When a message bounces, a full copy is sent to the originator, +/* and an optional copy of the diagnostics with message headers is +/* sent to the postmaster. The result is non-zero when the operation +/* should be tried again. +/* +/* When a bounce is sent, the sender address is the empty +/* address. +/* DIAGNOSTICS +/* Fatal error: error opening existing file. Warnings: corrupt +/* message file. A corrupt message is saved to the "corrupt" +/* queue for further inspection. +/* SEE ALSO +/* bounce(3) basic bounce service client interface +/* 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 +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "bounce_service.h" + +#define STR vstring_str + +/* bounce_notify_verp - send a bounce */ + +int bounce_notify_verp(char *service, char *queue_name, + char *queue_id, char *recipient, + char *verp_delims, int flush) +{ + char *myname = "bounce_notify_verp"; + BOUNCE_INFO *bounce_info; + int bounce_status = 0; + int postmaster_status; + VSTREAM *bounce; + int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks, + var_notify_classes); + char *postmaster; + VSTRING *verp_buf = vstring_alloc(100); + + /* + * Sanity checks. We must be called only for undeliverable non-bounce + * messages. + */ + if (*recipient == 0) + msg_panic("%s: attempt to bounce a single bounce", myname); + if (strcasecmp(recipient, mail_addr_double_bounce()) == 0) + msg_panic("%s: attempt to bounce a double bounce", myname); + + /* + * Initialize. Open queue file, bounce log, etc. + */ + bounce_info = bounce_mail_init(service, queue_name, queue_id, flush); + +#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */ +#define NULL_CLEANUP_FLAGS 0 +#define BOUNCE_HEADERS 1 +#define BOUNCE_ALL 0 + + /* + * A non-bounce message was returned. Send a single bounce, one per + * recipient. + */ + while (bounce_log_read(bounce_info->log_handle) != 0) { + + /* + * Notify the originator. + */ + verp_sender(verp_buf, verp_delims, recipient, + bounce_info->log_handle->recipient); + if ((bounce = post_mail_fopen_nowait(NULL_SENDER, STR(verp_buf), + NULL_CLEANUP_FLAGS)) != 0) { + + /* + * Send the bounce message header, some boilerplate text that + * pretends that we are a polite mail system, the text with + * reason for the bounce, and a copy of the original message. + */ + if (bounce_header(bounce, bounce_info, STR(verp_buf)) == 0 + && bounce_boilerplate(bounce, bounce_info) == 0 + && bounce_recipient_log(bounce, bounce_info) == 0 + && bounce_header_dsn(bounce, bounce_info) == 0 + && bounce_recipient_dsn(bounce, bounce_info) == 0) + bounce_original(bounce, bounce_info, flush ? + BOUNCE_ALL : BOUNCE_HEADERS); + bounce_status = post_mail_fclose(bounce); + } else + bounce_status = 1; + + /* + * Stop at the first sign of trouble, instead of making the problem + * worse. + */ + if (bounce_status != 0) + break; + + /* + * Mark this recipient as done. + */ + bounce_log_delrcpt(bounce_info->log_handle); + + /* + * Optionally, send a postmaster notice. + * + * This postmaster notice is not critical, so if it fails don't + * retransmit the bounce that we just generated, just log a warning. + */ +#define WANT_IF_BOUNCE (flush == 1 && (notify_mask & MAIL_ERROR_BOUNCE)) +#define WANT_IF_DELAY (flush == 0 && (notify_mask & MAIL_ERROR_DELAY)) + + if (WANT_IF_BOUNCE || WANT_IF_DELAY) { + + /* + * Send the text with reason for the bounce, and the headers of + * the original message. Don't bother sending the boiler-plate + * text. This postmaster notice is not critical, so if it fails + * don't retransmit the bounce that we just generated, just log a + * warning. + */ + postmaster = flush ? var_bounce_rcpt : var_delay_rcpt; + if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(), + postmaster, + NULL_CLEANUP_FLAGS)) != 0) { + if (bounce_header(bounce, bounce_info, postmaster) == 0 + && bounce_recipient_log(bounce, bounce_info) == 0 + && bounce_header_dsn(bounce, bounce_info) == 0 + && bounce_recipient_dsn(bounce, bounce_info) == 0) + bounce_original(bounce, bounce_info, BOUNCE_HEADERS); + postmaster_status = post_mail_fclose(bounce); + } else + postmaster_status = 1; + + if (postmaster_status) + msg_warn("postmaster notice failed while bouncing to %s", + recipient); + } + } + + /* + * Examine the completion status. Delete the bounce log file only when + * the bounce was posted successfully, and only if we are bouncing for + * real, not just warning. + */ + if (flush != 0 && bounce_status == 0 && mail_queue_remove(service, queue_id) + && errno != ENOENT) + msg_fatal("remove %s %s: %m", service, queue_id); + + /* + * Cleanup. + */ + bounce_mail_free(bounce_info); + vstring_free(verp_buf); + + return (bounce_status); +} diff --git a/postfix/src/bounce/bounce_service.h b/postfix/src/bounce/bounce_service.h index 369a3da0d..858609b79 100644 --- a/postfix/src/bounce/bounce_service.h +++ b/postfix/src/bounce/bounce_service.h @@ -28,6 +28,11 @@ extern int bounce_append_service(char *, char *, char *, char *); */ extern int bounce_notify_service(char *, char *, char *, char *, int); + /* + * bounce_notify_verp.c + */ +extern int bounce_notify_verp(char *, char *, char *, char *, char *, int); + /* * bounce_cleanup.c */ @@ -51,7 +56,7 @@ typedef struct { VSTREAM *orig_fp; /* open queue file */ long orig_offs; /* start of content */ time_t arrival_time; /* time of arrival */ - BOUNCE_LOG *log_handle; /* open logfile */ + BOUNCE_LOG *log_handle; /* open logfile */ } BOUNCE_INFO; extern BOUNCE_INFO *bounce_mail_init(const char *, const char *, const char *, int); diff --git a/postfix/src/cleanup/cleanup_envelope.c b/postfix/src/cleanup/cleanup_envelope.c index 86758c146..be3f727b6 100644 --- a/postfix/src/cleanup/cleanup_envelope.c +++ b/postfix/src/cleanup/cleanup_envelope.c @@ -179,6 +179,20 @@ static void cleanup_envelope_process(CLEANUP_STATE *state, int type, char *buf, state->errs |= CLEANUP_STAT_BAD; return; } + } else if (type == REC_TYPE_VERP) { + if (state->sender == 0 || *state->sender == 0) { + state->errs |= CLEANUP_STAT_BAD; + return; + } + if (len == 0) { + buf = var_verp_delim; + len = strlen(buf); + } + if (len == 2) { + cleanup_out(state, type, buf, len); + } else { + state->errs |= CLEANUP_STAT_BAD; + } } else { cleanup_out(state, type, buf, len); } diff --git a/postfix/src/cleanup/cleanup_init.c b/postfix/src/cleanup/cleanup_init.c index 0cb5812c5..29336362c 100644 --- a/postfix/src/cleanup/cleanup_init.c +++ b/postfix/src/cleanup/cleanup_init.c @@ -106,6 +106,7 @@ char *var_prop_extension; /* propagate unmatched extension */ char *var_always_bcc; /* big brother */ int var_extra_rcpt_limit; /* recipient extract limit */ char *var_rcpt_witheld; /* recipients not disclosed */ +char *var_verp_delim; /* default VERP delimiters */ CONFIG_INT_TABLE cleanup_int_table[] = { VAR_HOPCOUNT_LIMIT, DEF_HOPCOUNT_LIMIT, &var_hopcount_limit, 1, 0, @@ -133,6 +134,7 @@ CONFIG_STR_TABLE cleanup_str_table[] = { VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0, VAR_ALWAYS_BCC, DEF_ALWAYS_BCC, &var_always_bcc, 0, 0, VAR_RCPT_WITHELD, DEF_RCPT_WITHELD, &var_rcpt_witheld, 1, 0, + VAR_VERP_DELIM, DEF_VERP_DELIM, &var_verp_delim, 2, 2, 0, }; diff --git a/postfix/src/global/Makefile.in b/postfix/src/global/Makefile.in index b14c251a3..da8e25a62 100644 --- a/postfix/src/global/Makefile.in +++ b/postfix/src/global/Makefile.in @@ -18,7 +18,8 @@ SRCS = been_here.c bounce.c canon_addr.c cleanup_strerror.c clnt_stream.c \ sent.c smtp_stream.c split_addr.c string_list.c sys_exits.c \ timed_ipc.c tok822_find.c tok822_node.c tok822_parse.c \ tok822_resolve.c tok822_rewrite.c tok822_tree.c xtext.c bounce_log.c \ - flush_clnt.c mail_conf_time.c mbox_conf.c mbox_open.c abounce.c + flush_clnt.c mail_conf_time.c mbox_conf.c mbox_open.c abounce.c \ + verp_sender.c OBJS = been_here.o bounce.o canon_addr.o cleanup_strerror.o clnt_stream.o \ debug_peer.o debug_process.o defer.o deliver_completed.o \ deliver_flock.o deliver_pass.o deliver_request.o domain_list.o \ @@ -38,7 +39,8 @@ OBJS = been_here.o bounce.o canon_addr.o cleanup_strerror.o clnt_stream.o \ sent.o smtp_stream.o split_addr.o string_list.o sys_exits.o \ timed_ipc.o tok822_find.o tok822_node.o tok822_parse.o \ tok822_resolve.o tok822_rewrite.o tok822_tree.o xtext.o bounce_log.o \ - flush_clnt.o mail_conf_time.o mbox_conf.o mbox_open.o abounce.o + flush_clnt.o mail_conf_time.o mbox_conf.o mbox_open.o abounce.o \ + verp_sender.o HDRS = been_here.h bounce.h canon_addr.h cleanup_user.h clnt_stream.h \ config.h debug_peer.h debug_process.h defer.h deliver_completed.h \ deliver_flock.h deliver_pass.h deliver_request.h domain_list.h \ @@ -54,7 +56,7 @@ HDRS = been_here.h bounce.h canon_addr.h cleanup_user.h clnt_stream.h \ recipient_list.h record.h resolve_clnt.h resolve_local.h \ rewrite_clnt.h sent.h smtp_stream.h split_addr.h string_list.h \ sys_exits.h timed_ipc.h tok822.h xtext.h bounce_log.h flush_clnt.h \ - mbox_conf.h mbox_open.h abounce.h qmqp_proto.h + mbox_conf.h mbox_open.h abounce.h qmqp_proto.h verp_sender.h TESTSRC = rec2stream.c stream2rec.c recdump.c WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \ -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \ @@ -1036,6 +1038,11 @@ tok822_tree.o: ../../include/vstring.h tok822_tree.o: ../../include/vbuf.h tok822_tree.o: tok822.h tok822_tree.o: resolve_clnt.h +verp_sender.o: verp_sender.c +verp_sender.o: ../../include/sys_defs.h +verp_sender.o: ../../include/vstring.h +verp_sender.o: ../../include/vbuf.h +verp_sender.o: verp_sender.h xtext.o: xtext.c xtext.o: ../../include/sys_defs.h xtext.o: ../../include/vstream.h diff --git a/postfix/src/global/abounce.c b/postfix/src/global/abounce.c index bfda481d5..6f2302081 100644 --- a/postfix/src/global/abounce.c +++ b/postfix/src/global/abounce.c @@ -14,6 +14,15 @@ /* void (*callback)(int status, char *context); /* char *context; /* +/* void abounce_flush_verp(flags, queue, id, sender, verp, callback, context) +/* int flags; +/* const char *queue; +/* const char *id; +/* const char *sender; +/* const char *verp; +/* void (*callback)(int status, char *context); +/* char *context; +/* /* void adefer_flush(flags, queue, id, sender, callback, context) /* int flags; /* const char *queue; @@ -22,6 +31,15 @@ /* void (*callback)(int status, char *context); /* char *context; /* +/* void adefer_flush_verp(flags, queue, id, sender, verp, callback, context) +/* int flags; +/* const char *queue; +/* const char *id; +/* const char *sender; +/* const char *verp; +/* void (*callback)(int status, char *context); +/* char *context; +/* /* void adefer_warn(flags, queue, id, sender, callback, context) /* int flags; /* const char *queue; @@ -38,10 +56,16 @@ /* the specified sender, including the bounce log that was /* built with bounce_append(). /* +/* abounce_flush_verp() is like abounce_flush() but sends +/* one VERP style notification per undeliverable recipient. +/* /* adefer_flush() bounces the specified message to /* the specified sender, including the defer log that was /* built with defer_append(). /* +/* adefer_flush_verp() is like adefer_flush() but sends +/* one VERP style notification per undeliverable recipient. +/* /* adefer_warn() sends a "mail is delayed" notification to /* the specified sender, including the defer log that was /* built with defer_append(). @@ -64,6 +88,8 @@ /* file has the same name as the original message file. /* .IP sender /* The sender envelope address. +/* .IP verp +/* VERP delimiter characters. /* .IP callback /* Name of a routine that receives the notification status as /* documented for bounce_flush() or defer_flush(). @@ -148,6 +174,60 @@ static void abounce_event(int unused_event, char *context) abounce_done(ap, mail_scan(ap->fp, "%d", &status) == 1 ? status : -1); } +/* abounce_request_verp - suspend pseudo thread until server reply event */ + +static void abounce_request_verp(const char *class, const char *service, + int command, int flags, + const char *queue, const char *id, + const char *sender, const char *verp, + ABOUNCE_FN callback, + char *context) +{ + ABOUNCE *ap; + + /* + * Save pseudo thread state. Connect to the server. Send the request and + * suspend the pseudo thread until the server replies (or dies). + */ + ap = (ABOUNCE *) mymalloc(sizeof(*ap)); + ap->command = command; + ap->flags = flags; + ap->id = mystrdup(id); + ap->callback = callback; + ap->context = context; + ap->fp = mail_connect_wait(class, service); + + if (mail_print(ap->fp, "%d %d %s %s %s %s %s", command, + flags, queue, id, sender, verp, MAIL_EOF) == 0 + && vstream_fflush(ap->fp) == 0) { + event_enable_read(vstream_fileno(ap->fp), abounce_event, (char *) ap); + } else { + abounce_done(ap, -1); + } +} + +/* abounce_flush_verp - asynchronous bounce flush */ + +void abounce_flush_verp(int flags, const char *queue, const char *id, + const char *sender, const char *verp, + ABOUNCE_FN callback, char *context) +{ + abounce_request_verp(MAIL_CLASS_PRIVATE, MAIL_SERVICE_BOUNCE, + BOUNCE_CMD_VERP, flags, queue, id, sender, verp, + callback, context); +} + +/* adefer_flush_verp - asynchronous defer flush */ + +void adefer_flush_verp(int flags, const char *queue, const char *id, + const char *sender, const char *verp, + ABOUNCE_FN callback, char *context) +{ + abounce_request_verp(MAIL_CLASS_PRIVATE, MAIL_SERVICE_DEFER, + BOUNCE_CMD_VERP, flags, queue, id, sender, verp, + callback, context); +} + /* abounce_request - suspend pseudo thread until server reply event */ static void abounce_request(const char *class, const char *service, diff --git a/postfix/src/global/abounce.h b/postfix/src/global/abounce.h index 893224875..69542383a 100644 --- a/postfix/src/global/abounce.h +++ b/postfix/src/global/abounce.h @@ -25,6 +25,9 @@ extern void abounce_flush(int, const char *, const char *, const char *, ABOUNCE extern void adefer_flush(int, const char *, const char *, const char *, ABOUNCE_FN, char *); extern void adefer_warn(int, const char *, const char *, const char *, ABOUNCE_FN, char *); +extern void abounce_flush_verp(int, const char *, const char *, const char *, const char *, ABOUNCE_FN, char *); +extern void adefer_flush_verp(int, const char *, const char *, const char *, const char *, ABOUNCE_FN, char *); + /* LICENSE /* .ad /* .fi diff --git a/postfix/src/global/bounce.h b/postfix/src/global/bounce.h index 43ebc6040..018b91a4f 100644 --- a/postfix/src/global/bounce.h +++ b/postfix/src/global/bounce.h @@ -40,8 +40,8 @@ extern int vbounce_recip(int, const char *, const char *, const char *, */ #define BOUNCE_CMD_APPEND 0 /* append log */ #define BOUNCE_CMD_FLUSH 1 /* send log */ -#define BOUNCE_CMD_WARN 2 /* send warning bounce, don't delete - * log */ +#define BOUNCE_CMD_WARN 2 /* send warning, don't delete log */ +#define BOUNCE_CMD_VERP 3 /* send log, verp style */ /* * Flags. diff --git a/postfix/src/global/bounce_log.c b/postfix/src/global/bounce_log.c index 566ad4878..f0f841532 100644 --- a/postfix/src/global/bounce_log.c +++ b/postfix/src/global/bounce_log.c @@ -24,6 +24,9 @@ /* BOUNCE_LOG *bounce_log_read(bp) /* BOUNCE_LOG *bp; /* +/* BOUNCE_LOG *bounce_log_delrcpt(bp) +/* BOUNCE_LOG *bp; +/* /* void bounce_log_rewind(bp) /* BOUNCE_LOG *bp; /* @@ -31,8 +34,7 @@ /* BOUNCE_LOG *bp; /* DESCRIPTION /* This module implements a bounce/defer logfile API. Information -/* is sanitized for control and non-ASCII characters. Currently, -/* only the reading end is implemented. +/* is sanitized for control and non-ASCII characters. /* /* bounce_log_open() opens the named bounce or defer logfile /* and returns a handle that must be used for further access. @@ -47,6 +49,9 @@ /* bounce_log_read() returns a null pointer when no recipient was read, /* otherwise it returns its argument. /* +/* bounce_log_delrcpt() marks the last accessed recipient record as +/* "deleted". This requires that the logfile is opened for update. +/* /* bounce_log_rewind() is a helper that seeks to the first recipient /* in an open bounce or defer logfile (skipping over recipients that /* are marked as done). The result is 0 in case of success, -1 in case @@ -92,6 +97,7 @@ #include #include #include +#include /* Utility library. */ @@ -133,6 +139,7 @@ BOUNCE_LOG *bounce_log_open(const char *queue_name, const char *queue_id, bp->fp = fp; bp->buf = vstring_alloc(100); bp->status = STREQ(queue_name, MAIL_QUEUE_DEFER) ? "4.0.0" : "5.0.0"; + bp->offset = 0; return (bp); } } @@ -145,7 +152,8 @@ BOUNCE_LOG *bounce_log_read(BOUNCE_LOG *bp) char *text; char *cp; - while (vstring_get_nonl(bp->buf, bp->fp) != VSTREAM_EOF) { + while ((bp->offset = vstream_ftell(bp->fp)), + (vstring_get_nonl(bp->buf, bp->fp) != VSTREAM_EOF)) { if (STR(bp->buf)[0] == 0) continue; @@ -155,6 +163,12 @@ BOUNCE_LOG *bounce_log_read(BOUNCE_LOG *bp) */ cp = printable(STR(bp->buf), '?'); + /* + * Skip over deleted recipients. + */ + if (*cp == BOUNCE_LOG_STAT_DELETED) + continue; + /* * Find the recipient address. */ @@ -185,6 +199,21 @@ BOUNCE_LOG *bounce_log_read(BOUNCE_LOG *bp) return (0); } +/* bounce_log_delrcpt - mark recipient record as deleted */ + +BOUNCE_LOG *bounce_log_delrcpt(BOUNCE_LOG *bp) +{ + long current_offset; + + current_offset = vstream_ftell(bp->fp); + if (vstream_fseek(bp->fp, bp->offset, SEEK_SET) < 0) + msg_fatal("bounce logfile %s seek error: %m", VSTREAM_PATH(bp->fp)); + VSTREAM_PUTC(BOUNCE_LOG_STAT_DELETED, bp->fp); + if (vstream_fseek(bp->fp, current_offset, SEEK_SET) < 0) + msg_fatal("bounce logfile %s seek error: %m", VSTREAM_PATH(bp->fp)); + return (bp); +} + /* bounce_log_close - close bounce reader stream */ int bounce_log_close(BOUNCE_LOG *bp) diff --git a/postfix/src/global/bounce_log.h b/postfix/src/global/bounce_log.h index 7b949e951..0f91c6f8d 100644 --- a/postfix/src/global/bounce_log.h +++ b/postfix/src/global/bounce_log.h @@ -28,14 +28,18 @@ typedef struct { const char *recipient; /* final recipient */ const char *status; /* recipient status */ const char *text; /* why undeliverable */ + long offset; /* start of current record */ } BOUNCE_LOG; extern BOUNCE_LOG *bounce_log_open(const char *, const char *, int, int); extern BOUNCE_LOG *bounce_log_read(BOUNCE_LOG *); +extern BOUNCE_LOG *bounce_log_delrcpt(BOUNCE_LOG *); extern int bounce_log_close(BOUNCE_LOG *); #define bounce_log_rewind(bp) vstream_fseek((bp)->fp, 0L, SEEK_SET) +#define BOUNCE_LOG_STAT_DELETED 'D' /* deleted record */ + /* LICENSE /* .ad /* .fi diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index 1cfaf8755..c1c5b521b 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -1271,6 +1271,14 @@ extern int var_qmqpd_timeout; #define DEF_QMTPD_ERR_SLEEP "5s" extern int var_qmqpd_err_sleep; + /* + * VERP, more DJB intellectual cross-pollination. However, we prefer + as + * the default recipient delimiter. + */ +#define VAR_VERP_DELIM "default_verp_delimiters" +#define DEF_VERP_DELIM "+=" +extern char *var_verp_delim; + /* LICENSE /* .ad /* .fi diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index acd597ae9..7b5aa94c5 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -15,7 +15,7 @@ * Version of this program. */ #define VAR_MAIL_VERSION "mail_version" -#define DEF_MAIL_VERSION "Snapshot-20010707" +#define DEF_MAIL_VERSION "Snapshot-20010709" extern char *var_mail_version; /* LICENSE diff --git a/postfix/src/global/rec_type.c b/postfix/src/global/rec_type.c index c86a461aa..3b8f46f05 100644 --- a/postfix/src/global/rec_type.c +++ b/postfix/src/global/rec_type.c @@ -56,6 +56,7 @@ REC_TYPE_NAME rec_type_names[] = { REC_TYPE_RRTO, "return_receipt", REC_TYPE_ERTO, "errors_to", REC_TYPE_PRIO, "priority", + REC_TYPE_VERP, "verp_delimiters", REC_TYPE_END, "message_end", 0, 0, }; diff --git a/postfix/src/global/rec_type.h b/postfix/src/global/rec_type.h index 4412ee37d..1a89d8c95 100644 --- a/postfix/src/global/rec_type.h +++ b/postfix/src/global/rec_type.h @@ -45,6 +45,7 @@ #define REC_TYPE_RRTO 'r' /* return-receipt, from headers */ #define REC_TYPE_ERTO 'e' /* errors-to, from headers */ #define REC_TYPE_PRIO 'P' /* priority */ +#define REC_TYPE_VERP 'V' /* VERP delimiters */ #define REC_TYPE_END 'E' /* terminator, required */ @@ -53,7 +54,7 @@ * record groups. The first member in each set is the record type that * indicates the end of that record group. */ -#define REC_TYPE_ENVELOPE "MCTFILSDRW" +#define REC_TYPE_ENVELOPE "MCTFILSDRWV" #define REC_TYPE_CONTENT "XLN" #define REC_TYPE_EXTRACT "EDRPre" #define REC_TYPE_NOEXTRACT "E" diff --git a/postfix/src/global/split_addr.c b/postfix/src/global/split_addr.c index d9db5777b..3b1fbb3b3 100644 --- a/postfix/src/global/split_addr.c +++ b/postfix/src/global/split_addr.c @@ -67,7 +67,7 @@ char *split_addr(char *localpart, int delimiter) /* * Backwards compatibility: don't split owner-foo or foo-request. */ - if (var_ownreq_special != 0) { + if (delimiter == '-' && var_ownreq_special != 0) { if (strncasecmp(localpart, "owner-", 6) == 0) return (0); if ((len = strlen(localpart) - 8) > 0 diff --git a/postfix/src/global/verp_sender.c b/postfix/src/global/verp_sender.c new file mode 100644 index 000000000..8e88eb4bb --- /dev/null +++ b/postfix/src/global/verp_sender.c @@ -0,0 +1,83 @@ +/*++ +/* NAME +/* verp_sender 3 +/* SUMMARY +/* quote local part of mailbox +/* SYNOPSIS +/* #include +/* +/* VSTRING *verp_sender(dst, delims, sender, recipient) +/* VSTRING *dst; +/* const char *delims; +/* const char *sender; +/* const char *recipient; +/* DESCRIPTION +/* verp_sender() encodes the recipient address in the sender +/* address, using the specified delimiters. For example, +/* with delims +=, sender \fIprefix@origin\fR, and +/* recipient \fIuser@domain\fR the result is +/* \fIprefix+user=domain@origin\fR. +/* +/* Arguments: +/* .IP dst +/* The result. The buffer is null terminated. +/* .IP delims +/* VERP formatting characters. +/* .IP sender +/* Sender envelope address. +/* .IP recipient +/* Recipient envelope address. +/* 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 +#include + +/* Utility library. */ + +#include + +/* Global library. */ + +#include + +/* verp_sender - encode recipient into envelope sender address */ + +VSTRING *verp_sender(VSTRING *buf, const char *delimiters, + const char *sender, const char *recipient) +{ + int send_local_len; + int rcpt_local_len; + const char *cp; + + /* + * Change prefix@origin into prefix+user=domain@origin. + */ + send_local_len = ((cp = strrchr(sender, '@')) ? + cp - sender : strlen(sender)); + rcpt_local_len = ((cp = strrchr(recipient, '@')) ? + cp - recipient : strlen(recipient)); + vstring_strncpy(buf, sender, send_local_len); + VSTRING_ADDCH(buf, delimiters[0] & 0xff); + vstring_strncat(buf, recipient, rcpt_local_len); + if (recipient[rcpt_local_len] && recipient[rcpt_local_len + 1]) { + VSTRING_ADDCH(buf, delimiters[1] & 0xff); + vstring_strcat(buf, recipient + rcpt_local_len + 1); + } + if (sender[send_local_len] && sender[send_local_len + 1]) { + VSTRING_ADDCH(buf, '@'); + vstring_strcat(buf, sender + send_local_len + 1); + } + VSTRING_TERMINATE(buf); + return (buf); +} diff --git a/postfix/src/global/verp_sender.h b/postfix/src/global/verp_sender.h new file mode 100644 index 000000000..11beb6754 --- /dev/null +++ b/postfix/src/global/verp_sender.h @@ -0,0 +1,35 @@ +#ifndef _VERP_SENDER_H_INCLUDED_ +#define _VERP_SENDER_H_INCLUDED_ + +/*++ +/* NAME +/* verp_sender 3h +/* SUMMARY +/* encode recipient into sender, VERP style +/* SYNOPSIS +/* #include "verp_sender.h" +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern VSTRING *verp_sender(VSTRING *, const char *, const char *, const char *); + +/* 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 +/*--*/ + +#endif diff --git a/postfix/src/nqmgr/qmgr.h b/postfix/src/nqmgr/qmgr.h index e32c64bf4..2afbf440b 100644 --- a/postfix/src/nqmgr/qmgr.h +++ b/postfix/src/nqmgr/qmgr.h @@ -267,6 +267,7 @@ struct QMGR_MESSAGE { char *queue_name; /* queue name */ char *queue_id; /* queue file */ char *sender; /* complete address */ + char *verp_delims; /* VERP delimiters */ char *errors_to; /* error report address */ char *return_receipt; /* confirm receipt address */ char *filter_xport; /* filtering transport */ diff --git a/postfix/src/nqmgr/qmgr_active.c b/postfix/src/nqmgr/qmgr_active.c index 404c4eda9..847f42713 100644 --- a/postfix/src/nqmgr/qmgr_active.c +++ b/postfix/src/nqmgr/qmgr_active.c @@ -275,12 +275,21 @@ void qmgr_active_done(QMGR_MESSAGE *message) } else { if (msg_verbose) msg_info("%s: bounce %s", myname, message->queue_id); - abounce_flush(BOUNCE_FLAG_KEEP, - message->queue_name, - message->queue_id, - message->errors_to, - qmgr_active_done_2_bounce_flush, - (char *) message); + if (message->verp_delims == 0) + abounce_flush(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->errors_to, + qmgr_active_done_2_bounce_flush, + (char *) message); + else + abounce_flush_verp(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->errors_to, + message->verp_delims, + qmgr_active_done_2_bounce_flush, + (char *) message); return; } } @@ -353,12 +362,21 @@ static void qmgr_active_done_2_generic(QMGR_MESSAGE *message) if (event_time() > message->arrival_time + var_max_queue_time) { if (msg_verbose) msg_info("%s: too old, bouncing %s", myname, message->queue_id); - adefer_flush(BOUNCE_FLAG_KEEP, - message->queue_name, - message->queue_id, - message->errors_to, - qmgr_active_done_3_defer_flush, - (char *) message); + if (message->verp_delims == 0) + adefer_flush(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->errors_to, + qmgr_active_done_3_defer_flush, + (char *) message); + else + adefer_flush_verp(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->errors_to, + message->verp_delims, + qmgr_active_done_3_defer_flush, + (char *) message); return; } else if (message->warn_time > 0 && event_time() > message->warn_time) { diff --git a/postfix/src/nqmgr/qmgr_deliver.c b/postfix/src/nqmgr/qmgr_deliver.c index 8a06746ce..d8e513122 100644 --- a/postfix/src/nqmgr/qmgr_deliver.c +++ b/postfix/src/nqmgr/qmgr_deliver.c @@ -68,6 +68,7 @@ #include #include #include +#include /* Application-specific. */ @@ -124,6 +125,22 @@ static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream) QMGR_RCPT *recipient; QMGR_MESSAGE *message = entry->message; char *cp; + VSTRING *sender_buf = 0; + char *sender; + + /* + * If variable envelope return path is requested, change prefix+@origin + * into prefix+user=domain@origin. Note that with VERP there is only one + * recipient per delivery. + */ + if (message->verp_delims == 0) { + sender = message->sender; + } else { + sender_buf = vstring_alloc(100); + verp_sender(sender_buf, message->verp_delims, + message->sender, list.info->address); + sender = vstring_str(sender_buf); + } /* * With mail transports that accept only one recipient per delivery, the @@ -136,9 +153,11 @@ static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream) message->queue_name, message->queue_id, message->data_offset, message->data_size, (cp = strrchr(entry->queue->name, '@')) != 0 && cp[1] ? cp + 1 : - entry->queue->name, message->sender, + entry->queue->name, sender, message->errors_to, message->return_receipt, message->arrival_time); + if (sender_buf != 0) + vstring_free(sender_buf); for (recipient = list.info; recipient < list.info + list.len; recipient++) mail_print(stream, "%ld %s", recipient->offset, recipient->address); mail_print(stream, "%s", "0"); diff --git a/postfix/src/nqmgr/qmgr_message.c b/postfix/src/nqmgr/qmgr_message.c index 5d56eba03..1fcbb6d27 100644 --- a/postfix/src/nqmgr/qmgr_message.c +++ b/postfix/src/nqmgr/qmgr_message.c @@ -161,6 +161,7 @@ static QMGR_MESSAGE *qmgr_message_create(const char *queue_name, message->warn_offset = 0; message->warn_time = 0; message->rcpt_offset = 0; + message->verp_delims = 0; message->unread_offset = 0; qmgr_rcpt_list_init(&message->rcpt_list); message->rcpt_count = 0; @@ -423,6 +424,14 @@ static int qmgr_message_read(QMGR_MESSAGE *message) message->warn_offset = curr_offset; message->warn_time = atol(start); } + } else if (rec_type == REC_TYPE_VERP) { + if (strlen(start) != 2) { + msg_warn("%s: bad VERP record length: \"%s\"", + message->queue_id, start); + } else { + message->single_rcpt = 1; + message->verp_delims = mystrdup(start); + } } } while (rec_type > 0 && rec_type != REC_TYPE_END); @@ -902,6 +911,8 @@ void qmgr_message_free(QMGR_MESSAGE *message) myfree(message->queue_name); if (message->sender) myfree(message->sender); + if (message->verp_delims) + myfree(message->verp_delims); if (message->errors_to) myfree(message->errors_to); if (message->return_receipt) diff --git a/postfix/src/qmgr/Makefile.in b/postfix/src/qmgr/Makefile.in index c37f1d78f..db3885567 100644 --- a/postfix/src/qmgr/Makefile.in +++ b/postfix/src/qmgr/Makefile.in @@ -136,6 +136,7 @@ qmgr_deliver.o: ../../include/mail_proto.h qmgr_deliver.o: ../../include/recipient_list.h qmgr_deliver.o: ../../include/mail_params.h qmgr_deliver.o: ../../include/deliver_request.h +qmgr_deliver.o: ../../include/verp_sender.h qmgr_deliver.o: qmgr.h qmgr_deliver.o: ../../include/scan_dir.h qmgr_deliver.o: ../../include/maps.h diff --git a/postfix/src/qmgr/qmgr.h b/postfix/src/qmgr/qmgr.h index 45ceac8e9..db55af124 100644 --- a/postfix/src/qmgr/qmgr.h +++ b/postfix/src/qmgr/qmgr.h @@ -227,6 +227,7 @@ struct QMGR_MESSAGE { char *queue_name; /* queue name */ char *queue_id; /* queue file */ char *sender; /* complete address */ + char *verp_delims; /* VERP delimiters */ char *errors_to; /* error report address */ char *return_receipt; /* confirm receipt address */ char *filter_xport; /* filtering transport */ diff --git a/postfix/src/qmgr/qmgr_active.c b/postfix/src/qmgr/qmgr_active.c index 404c4eda9..847f42713 100644 --- a/postfix/src/qmgr/qmgr_active.c +++ b/postfix/src/qmgr/qmgr_active.c @@ -275,12 +275,21 @@ void qmgr_active_done(QMGR_MESSAGE *message) } else { if (msg_verbose) msg_info("%s: bounce %s", myname, message->queue_id); - abounce_flush(BOUNCE_FLAG_KEEP, - message->queue_name, - message->queue_id, - message->errors_to, - qmgr_active_done_2_bounce_flush, - (char *) message); + if (message->verp_delims == 0) + abounce_flush(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->errors_to, + qmgr_active_done_2_bounce_flush, + (char *) message); + else + abounce_flush_verp(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->errors_to, + message->verp_delims, + qmgr_active_done_2_bounce_flush, + (char *) message); return; } } @@ -353,12 +362,21 @@ static void qmgr_active_done_2_generic(QMGR_MESSAGE *message) if (event_time() > message->arrival_time + var_max_queue_time) { if (msg_verbose) msg_info("%s: too old, bouncing %s", myname, message->queue_id); - adefer_flush(BOUNCE_FLAG_KEEP, - message->queue_name, - message->queue_id, - message->errors_to, - qmgr_active_done_3_defer_flush, - (char *) message); + if (message->verp_delims == 0) + adefer_flush(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->errors_to, + qmgr_active_done_3_defer_flush, + (char *) message); + else + adefer_flush_verp(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->errors_to, + message->verp_delims, + qmgr_active_done_3_defer_flush, + (char *) message); return; } else if (message->warn_time > 0 && event_time() > message->warn_time) { diff --git a/postfix/src/qmgr/qmgr_deliver.c b/postfix/src/qmgr/qmgr_deliver.c index 1a605dd1f..3614690ca 100644 --- a/postfix/src/qmgr/qmgr_deliver.c +++ b/postfix/src/qmgr/qmgr_deliver.c @@ -63,6 +63,7 @@ #include #include #include +#include /* Application-specific. */ @@ -119,6 +120,22 @@ static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream) QMGR_RCPT *recipient; QMGR_MESSAGE *message = entry->message; char *cp; + VSTRING *sender_buf = 0; + char *sender; + + /* + * If variable envelope return path is requested, change prefix+@origin + * into prefix+user=domain@origin. Note that with VERP there is only one + * recipient per delivery. + */ + if (message->verp_delims == 0) { + sender = message->sender; + } else { + sender_buf = vstring_alloc(100); + verp_sender(sender_buf, message->verp_delims, + message->sender, list.info->address); + sender = vstring_str(sender_buf); + } /* * With mail transports that accept only one recipient per delivery, the @@ -131,9 +148,11 @@ static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream) message->queue_name, message->queue_id, message->data_offset, message->data_size, (cp = strrchr(entry->queue->name, '@')) != 0 && cp[1] ? cp + 1 : - entry->queue->name, message->sender, + entry->queue->name, sender, message->errors_to, message->return_receipt, message->arrival_time); + if (sender_buf != 0) + vstring_free(sender_buf); for (recipient = list.info; recipient < list.info + list.len; recipient++) mail_print(stream, "%ld %s", recipient->offset, recipient->address); mail_print(stream, "%s", "0"); diff --git a/postfix/src/qmgr/qmgr_message.c b/postfix/src/qmgr/qmgr_message.c index e696fb4a7..db88b63fc 100644 --- a/postfix/src/qmgr/qmgr_message.c +++ b/postfix/src/qmgr/qmgr_message.c @@ -151,6 +151,7 @@ static QMGR_MESSAGE *qmgr_message_create(const char *queue_name, message->warn_offset = 0; message->warn_time = 0; message->rcpt_offset = 0; + message->verp_delims = 0; qmgr_rcpt_list_init(&message->rcpt_list); return (message); } @@ -303,6 +304,14 @@ static int qmgr_message_read(QMGR_MESSAGE *message) message->warn_offset = curr_offset; message->warn_time = atol(start); } + } else if (rec_type == REC_TYPE_VERP) { + if (strlen(start) != 2) { + msg_warn("%s: bad VERP record length: \"%s\"", + message->queue_id, start); + } else { + message->single_rcpt = 1; + message->verp_delims = mystrdup(start); + } } } while (rec_type > 0 && rec_type != REC_TYPE_END); @@ -737,6 +746,8 @@ void qmgr_message_free(QMGR_MESSAGE *message) myfree(message->queue_name); if (message->sender) myfree(message->sender); + if (message->verp_delims) + myfree(message->verp_delims); if (message->errors_to) myfree(message->errors_to); if (message->return_receipt) diff --git a/postfix/src/qmqpd/qmqpd.c b/postfix/src/qmqpd/qmqpd.c index 281e472c2..5117d399c 100644 --- a/postfix/src/qmqpd/qmqpd.c +++ b/postfix/src/qmqpd/qmqpd.c @@ -210,11 +210,34 @@ static void qmqpd_read_content(QMQPD_STATE *state) static void qmqpd_copy_sender(QMQPD_STATE *state) { + char *end_prefix; + char *end_origin; + int verp_requested; + + /* + * If the sender address looks like prefix-@origin-@[], then request + * variable envelope return path delivery, with an envelope sender + * address of prefix@origin, and with VERP delimiters of - and =. This + * way, the recipients will see envelope sender addresses that look like: + * prefix-user=domain@origin. + */ state->where = "receiving sender address"; netstring_get(state->client, state->buf, var_line_limit); + verp_requested = ((end_prefix = strstr(STR(state->buf), "-@")) != 0 + && (end_origin = strstr(end_prefix + 2, "-@")) != 0 + && strncmp(end_origin + 2, "[]", 2) == 0 + && vstring_end(state->buf) == end_origin + 4); + if (verp_requested) { + memcpy(end_prefix, end_prefix + 1, end_origin - end_prefix - 1); + vstring_truncate(state->buf, end_origin - STR(state->buf) - 1); + } if (state->err == CLEANUP_STAT_OK && REC_PUT_BUF(state->cleanup, REC_TYPE_FROM, state->buf) < 0) state->err = CLEANUP_STAT_WRITE; + if (verp_requested) + if (state->err == CLEANUP_STAT_OK + && rec_put(state->cleanup, REC_TYPE_VERP, "-=", 2) < 0) + state->err = CLEANUP_STAT_WRITE; state->sender = mystrndup(STR(state->buf), LEN(state->buf)); } diff --git a/postfix/src/sendmail/sendmail.c b/postfix/src/sendmail/sendmail.c index a3daf6422..a11837ef4 100644 --- a/postfix/src/sendmail/sendmail.c +++ b/postfix/src/sendmail/sendmail.c @@ -76,6 +76,11 @@ /* \fBdebug_peer_level\fR configuration parameters instead. /* .IP "\fB-U\fR (ignored)" /* Initial user submission. +/* .IP \fB-V\fR +/* Variable Envelope Return Path. Given an envelope sender address +/* \fIprefix\fR-@\fIorigin\fR, each recipient \fIuser@domain\fR +/* receives mail with a personalized envelope sender address +/* \fIprefix\fB-\fIuser=domain\fR@\fIorigin\fR. /* .IP \fB-bd\fR /* Go into daemon mode. This mode of operation is implemented by /* executing the \fBpostfix start\fR command. @@ -319,6 +324,11 @@ static void sendmail_cleanup(void); #define SM_FLAG_DEFAULT (SM_FLAG_AEOF) + /* + * VERP support. + */ +char *verp_delims; + /* * Silly little macros (SLMs). */ @@ -414,6 +424,10 @@ static void enqueue(const int flags, const char *sender, const char *full_name, if (full_name || (full_name = fullname()) != 0) rec_fputs(dst, REC_TYPE_FULL, full_name); rec_fputs(dst, REC_TYPE_FROM, saved_sender); + if (verp_delims && *saved_sender == 0) + msg_fatal("-V option requires non-null sender address"); + if (verp_delims) + rec_fputs(dst, REC_TYPE_VERP, verp_delims); if (recipients) { for (cpp = recipients; *cpp != 0; cpp++) { tree = tok822_parse(*cpp); @@ -794,7 +808,7 @@ int main(int argc, char **argv) optind++; continue; } - if ((c = GETOPT(argc, argv, "B:C:F:GIN:R:UX:b:ce:f:h:imno:p:r:q:tvx")) <= 0) + if ((c = GETOPT(argc, argv, "B:C:F:GIN:R:UVX:b:ce:f:h:imno:p:r:q:tvx")) <= 0) break; switch (c) { default: @@ -817,6 +831,9 @@ int main(int argc, char **argv) break; case 'R': /* DSN */ break; + case 'V': /* VERP */ + verp_delims = ""; + break; case 'b': switch (*optarg) { default: diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c index 5353a8e60..3bc59cb3d 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -32,8 +32,8 @@ /* STANDARDS /* RFC 821 (SMTP protocol) /* RFC 1123 (Host requirements) -/* RFC 1651 (SMTP service extensions) /* RFC 1652 (8bit-MIME transport) +/* RFC 1869 (SMTP service extensions) /* RFC 1854 (SMTP Pipelining) /* RFC 1870 (Message Size Declaration) /* RFC 1985 (ETRN command) @@ -363,6 +363,12 @@ char *smtpd_path; #define STR(x) vstring_str(x) #define LEN(x) VSTRING_LEN(x) + /* + * VERP command name. + */ +#define VERP_CMD "XVERP" +#define VERP_CMD_LEN 5 + /* * Forward declarations. */ @@ -459,6 +465,7 @@ static int ehlo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) smtpd_chat_reply(state, "250-AUTH=%s", state->sasl_mechanism_list); } #endif + smtpd_chat_reply(state, "250-%s", VERP_CMD); smtpd_chat_reply(state, "250 8BITMIME"); return (0); } @@ -626,6 +633,7 @@ static int mail_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) char *err; int narg; char *arg; + char *verp_delims = 0; state->msg_size = 0; @@ -680,12 +688,27 @@ static int mail_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) return (-1); } #endif + } else if (strcasecmp(arg, VERP_CMD) == 0) { + verp_delims = ""; + } else if (strncasecmp(arg, VERP_CMD, VERP_CMD_LEN) == 0 + && arg[VERP_CMD_LEN] == '=') { + verp_delims = arg + VERP_CMD_LEN + 1; + if (strlen(verp_delims) != 2) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 Bad %s parameter: %s", + VERP_CMD, arg); + return (-1); + } } else { state->error_mask |= MAIL_ERROR_PROTOCOL; smtpd_chat_reply(state, "555 Unsupported option: %s", arg); return (-1); } } + if (verp_delims && argv[2].strval[0] == 0) { + smtpd_chat_reply(state, "503 Error: XVERP requires non-null sender"); + return (-1); + } state->time = time((time_t *) 0); if (SMTPD_STAND_ALONE(state) == 0 && var_smtpd_delay_reject == 0 @@ -718,6 +741,8 @@ static int mail_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) if (*var_filter_xport) rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s", var_filter_xport); rec_fputs(state->cleanup, REC_TYPE_FROM, argv[2].strval); + if (verp_delims) + rec_fputs(state->cleanup, REC_TYPE_VERP, verp_delims); state->sender = mystrdup(argv[2].strval); smtpd_chat_reply(state, "250 Ok"); return (0); diff --git a/postfix/src/smtpstone/qmqp-sink.c b/postfix/src/smtpstone/qmqp-sink.c index 53b39584c..1c696b899 100644 --- a/postfix/src/smtpstone/qmqp-sink.c +++ b/postfix/src/smtpstone/qmqp-sink.c @@ -8,7 +8,7 @@ /* \fBqmqp-sink\fR [\fB-cv\fR] [\fB-x \fItime\fR] /* [\fBinet:\fR][\fIhost\fR]:\fIport\fR \fIbacklog\fR /* -/* \fBqmqp-sink\fR [\fB-cv\fR] +/* \fBqmqp-sink\fR [\fB-cv\fR] [\fB-x \fItime\fR] /* \fBunix:\fR\fIpathname\fR \fIbacklog\fR /* DESCRIPTION /* \fIqmqp-sink\fR listens on the named host (or address) and port.