+-TABOUNCE
-TALIAS_TOKEN
-TARGV
-TBH_TABLE
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)
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)
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
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
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.
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
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:
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
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.
-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
--- /dev/null
+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:<sender@domain> XVERP
+ MAIL FROM:<sender@domain> 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.
<b>-U</b> (ignored)
Initial user submission.
- <b>-bd</b> Go into daemon mode. This mode of operation is
+ <b>-V</b> Variable Envelope Return Path. Given an envelope
+ sender address <i>prefix</i>-@<i>origin</i>, each recipient
+ <i>user@domain</i> receives mail with a personalized enve-
+ lope sender address <i>prefix</i><b>-</b><i>user=domain</i>@<i>origin</i>.
+
+ <b>-bd</b> Go into daemon mode. This mode of operation is
implemented by executing the <b>postfix</b> <b>start</b> command.
- <b>-bi</b> Initialize alias database. See the <b>newaliases</b> com-
+ <b>-bi</b> Initialize alias database. See the <b>newaliases</b> com-
mand above.
- <b>-bm</b> Read mail from standard input and arrange for
+ <b>-bm</b> Read mail from standard input and arrange for
delivery. This is the default mode of operation.
<b>-bp</b> List the mail queue. See the <b>mailq</b> command above.
- <b>-bs</b> Stand-alone SMTP server mode. Read SMTP commands
- from standard input, and write responses to stan-
+ <b>-bs</b> 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 <a href="smtpd.8.html"><b>smtpd</b>(8)</a> daemon.
<b>-f</b> <i>sender</i>
Set the envelope sender address. This is the
address where delivery problems are sent to, unless
- the message contains an <b>Errors-To:</b> message header.
+ the message contains an <b>Errors-To:</b> message header.
<b>-h</b> <i>hop_count</i> (ignored)
- Hop count limit. Use the <b>hopcount</b><i>_</i><b>limit</b> configura-
+ Hop count limit. Use the <b>hopcount</b><i>_</i><b>limit</b> configura-
tion parameter instead.
- <b>-i</b> When reading a message from standard input, don't
- treat a line with only a <b>.</b> character as the end of
+ <b>-i</b> When reading a message from standard input, don't
+ treat a line with only a <b>.</b> character as the end of
input.
<b>-m</b> (ignored)
Backwards compatibility.
<b>-oA</b><i>alias_database</i>
- Non-default alias database. Specify <i>pathname</i> or
+ Non-default alias database. Specify <i>pathname</i> or
<i>type</i>:<i>pathname</i>. See <a href="postalias.1.html"><b>postalias</b>(1)</a> for details.
<b>-o7</b> (ignored)
<b>-o8</b> (ignored)
- The message body type. Currently, Postfix imple-
+ The message body type. Currently, Postfix imple-
ments <b>just-send-eight</b>.
- <b>-oi</b> When reading a message from standard input, don't
- treat a line with only a <b>.</b> character as the end of
+ <b>-oi</b> When reading a message from standard input, don't
+ treat a line with only a <b>.</b> character as the end of
input.
<b>-om</b> (ignored)
- The sender is never eliminated from alias etc.
+ The sender is never eliminated from alias etc.
expansions.
<b>-o</b> <i>x</i> <i>value</i> (ignored)
- Set option <i>x</i> to <i>value</i>. Use the equivalent configu-
+ Set option <i>x</i> to <i>value</i>. Use the equivalent configu-
ration parameter in <b>main.cf</b> instead.
<b>-r</b> <i>sender</i>
Set the envelope sender address. This is the
address where delivery problems are sent to, unless
- the message contains an <b>Errors-To:</b> message header.
+ the message contains an <b>Errors-To:</b> message header.
- <b>-q</b> Attempt to deliver all queued mail. This is imple-
+ <b>-q</b> Attempt to deliver all queued mail. This is imple-
mented by kicking the <a href="qmgr.8.html"><b>qmgr</b>(8)</a> daemon.
<b>-q</b><i>interval</i> (ignored)
- The interval between queue runs. Use the
+ The interval between queue runs. Use the
<b>queue</b><i>_</i><b>run</b><i>_</i><b>delay</b> configuration parameter instead.
<b>-qR</b><i>site</i>
- Schedule immediate delivery of all mail that is
- queued for the named <i>site</i>. Depending on the desti-
- nation, this uses "fast flush" service, or it has
- the same effect as <b>sendmail</b> <b>-q</b>. This is imple-
+ Schedule immediate delivery of all mail that is
+ queued for the named <i>site</i>. Depending on the desti-
+ nation, this uses "fast flush" service, or it has
+ the same effect as <b>sendmail</b> <b>-q</b>. This is imple-
mented by connecting to the local SMTP server. See
<a href="smtpd.8.html"><b>smtpd</b>(8)</a> for more information about the "fast
flush" service.
<b>-qS</b><i>site</i>
- This command is not implemented. Use the slower
+ This command is not implemented. Use the slower
<b>sendmail</b> <b>-q</b> command instead.
- <b>-t</b> Extract recipients from message headers. This
- requires that no recipients be specified on the
+ <b>-t</b> Extract recipients from message headers. This
+ requires that no recipients be specified on the
command line.
<b>-v</b> Enable verbose logging for debugging purposes. Mul-
- tiple <b>-v</b> options make the software increasingly
+ tiple <b>-v</b> options make the software increasingly
verbose.
<b>SECURITY</b>
- 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.
<b>DIAGNOSTICS</b>
- Problems are logged to <b>syslogd</b>(8) and to the standard
+ Problems are logged to <b>syslogd</b>(8) and to the standard
error stream.
<b>ENVIRONMENT</b>
<b>MAIL</b><i>_</i><b>DEBUG</b>
Enable debugging with an external command, as spec-
- ified with the <b>debugger</b><i>_</i><b>command</b> configuration
+ ified with the <b>debugger</b><i>_</i><b>command</b> configuration
parameter.
<b>FILES</b>
/etc/postfix, configuration files
<b>CONFIGURATION</b> <b>PARAMETERS</b>
- See the Postfix <b>main.cf</b> file for syntax details and for
- default values. Use the <b>postfix</b> <b>reload</b> command after a
+ See the Postfix <b>main.cf</b> file for syntax details and for
+ default values. Use the <b>postfix</b> <b>reload</b> command after a
configuration change.
<b>alias</b><i>_</i><b>database</b>
- Default alias database(s) for <b>newaliases</b>. The
- default value for this parameter is system-spe-
+ Default alias database(s) for <b>newaliases</b>. The
+ default value for this parameter is system-spe-
cific.
<b>bounce</b><i>_</i><b>size</b><i>_</i><b>limit</b>
initialized.
<b>debug</b><i>_</i><b>peer</b><i>_</i><b>level</b>
- Increment in verbose logging level when a remote
+ Increment in verbose logging level when a remote
host matches a pattern in the <b>debug</b><i>_</i><b>peer</b><i>_</i><b>list</b>
parameter.
<b>debug</b><i>_</i><b>peer</b><i>_</i><b>list</b>
- 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
<b>debug</b><i>_</i><b>peer</b><i>_</i><b>level</b> parameter.
<b>fast</b><i>_</i><b>flush</b><i>_</i><b>domains</b>
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 <b>ETRN</b> request, and of
- the <b>sendmail</b> <b>-qR</b> 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 <b>ETRN</b> request, and of
+ the <b>sendmail</b> <b>-qR</b> command. For domains not in the
list, Postfix simply attempts to deliver all queued
mail.
<b>fork</b><i>_</i><b>attempts</b>
- Number of attempts to <b>fork</b>() a process before giv-
+ Number of attempts to <b>fork</b>() a process before giv-
ing up.
<b>fork</b><i>_</i><b>delay</b>
- Delay in seconds between successive <b>fork</b>()
+ Delay in seconds between successive <b>fork</b>()
attempts.
<b>hopcount</b><i>_</i><b>limit</b>
Limit the number of <b>Received:</b> message headers.
<b>mail</b><i>_</i><b>owner</b>
- The owner of the mail queue and of most Postfix
+ The owner of the mail queue and of most Postfix
processes.
<b>command</b><i>_</i><b>directory</b>
- Directory with Postfix support commands (default:
+ Directory with Postfix support commands (default:
<b>$program</b><i>_</i><b>directory</b>).
<b>daemon</b><i>_</i><b>directory</b>
- Directory with Postfix daemon programs (default:
+ Directory with Postfix daemon programs (default:
<b>$program</b><i>_</i><b>directory</b>).
<b>queue</b><i>_</i><b>directory</b>
- 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.
<b>queue</b><i>_</i><b>run</b><i>_</i><b>delay</b>
- The time between successive scans of the deferred
+ The time between successive scans of the deferred
queue.
<b>SEE</b> <b>ALSO</b>
syslogd(8) system logging
<b>LICENSE</b>
- The Secure Mailer license must be distributed with this
+ The Secure Mailer license must be distributed with this
software.
<b>AUTHOR(S)</b>
<b>STANDARDS</b>
<a href="http://www.faqs.org/rfcs/rfc821.html">RFC 821</a> (SMTP protocol)
<a href="http://www.faqs.org/rfcs/rfc1123.html">RFC 1123</a> (Host requirements)
- <a href="http://www.faqs.org/rfcs/rfc1651.html">RFC 1651</a> (SMTP service extensions)
<a href="http://www.faqs.org/rfcs/rfc1652.html">RFC 1652</a> (8bit-MIME transport)
+ <a href="http://www.faqs.org/rfcs/rfc1869.html">RFC 1869</a> (SMTP service extensions)
<a href="http://www.faqs.org/rfcs/rfc1854.html">RFC 1854</a> (SMTP Pipelining)
<a href="http://www.faqs.org/rfcs/rfc1870.html">RFC 1870</a> (Message Size Declaration)
<a href="http://www.faqs.org/rfcs/rfc1985.html">RFC 1985</a> (ETRN command)
\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.
.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)
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 \
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
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
/* System library. */
#include <sys_defs.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
/* Utility library. */
#include <mail_params.h>
#include <mail_conf.h>
#include <bounce.h>
+#include <mail_addr.h>
/* Single-threaded server skeleton. */
static VSTRING *queue_name;
static VSTRING *recipient;
static VSTRING *sender;
+static VSTRING *verp_delims;
static VSTRING *why;
#define STR vstring_str
* 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 */
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) {
queue_name = vstring_alloc(10);
recipient = vstring_alloc(10);
sender = vstring_alloc(10);
+ verp_delims = vstring_alloc(10);
why = vstring_alloc(10);
}
*/
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);
--- /dev/null
+/*++
+/* 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 <sys_defs.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstream.h>
+#include <name_mask.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_queue.h>
+#include <post_mail.h>
+#include <mail_addr.h>
+#include <mail_error.h>
+#include <verp_sender.h>
+
+/* 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);
+}
*/
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
*/
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);
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);
}
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,
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,
};
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 \
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 \
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 \
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
/* 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;
/* 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;
/* 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().
/* 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().
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,
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
*/
#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.
/* 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;
/*
/* 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.
/* 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
#include <sys_defs.h>
#include <string.h>
#include <ctype.h>
+#include <unistd.h>
/* Utility library. */
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);
}
}
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;
*/
cp = printable(STR(bp->buf), '?');
+ /*
+ * Skip over deleted recipients.
+ */
+ if (*cp == BOUNCE_LOG_STAT_DELETED)
+ continue;
+
/*
* Find the recipient address.
*/
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)
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
#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
* 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
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,
};
#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 */
* 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"
/*
* 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
--- /dev/null
+/*++
+/* NAME
+/* verp_sender 3
+/* SUMMARY
+/* quote local part of mailbox
+/* SYNOPSIS
+/* #include <verp_sender.h>
+/*
+/* 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 <sys_defs.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+
+/* Global library. */
+
+#include <verp_sender.h>
+
+/* 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);
+}
--- /dev/null
+#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 <vstring.h>
+
+ /*
+ * 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
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 */
} 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;
}
}
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) {
#include <recipient_list.h>
#include <mail_params.h>
#include <deliver_request.h>
+#include <verp_sender.h>
/* Application-specific. */
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
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");
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;
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);
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)
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
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 */
} 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;
}
}
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) {
#include <recipient_list.h>
#include <mail_params.h>
#include <deliver_request.h>
+#include <verp_sender.h>
/* Application-specific. */
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
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");
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);
}
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);
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)
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));
}
/* \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.
#define SM_FLAG_DEFAULT (SM_FLAG_AEOF)
+ /*
+ * VERP support.
+ */
+char *verp_delims;
+
/*
* Silly little macros (SLMs).
*/
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);
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:
break;
case 'R': /* DSN */
break;
+ case 'V': /* VERP */
+ verp_delims = "";
+ break;
case 'b':
switch (*optarg) {
default:
/* 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)
#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.
*/
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);
}
char *err;
int narg;
char *arg;
+ char *verp_delims = 0;
state->msg_size = 0;
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
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);
/* \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.