limit in the SMTP server didn't work for size limits close
enough to INT_MAX. File: smtpd/smtpd.c.
- Bugfix: after an SMTP client was rejected with "smtpd_delay_reject
- = no", the SMTP server would panic as it generated spurious
- Milter requests for unrecognized commands. File: smtpd/smtpd.c.
+ Bugfix (introduced Postfix 2.3): after an SMTP client was
+ rejected with "smtpd_delay_reject = no", the SMTP server
+ would panic as it generated spurious Milter requests for
+ unrecognized commands. File: smtpd/smtpd.c.
20060727
20060805
- Bugfix: #ifdef damage caused smtp_sasl_start() to be invoked
- twice. Reported by C-J Lofstedt. File: smtp/smtp_sasl_proto.c.
+ Bugfix (introduced Postfix 2.3): #ifdef damage caused
+ smtp_sasl_start() to be invoked twice. Reported by C-J
+ Lofstedt. File: smtp/smtp_sasl_proto.c.
20060806
helpdesk service that solves all their email problems.
Credits to Jonathan Balester. File: bounce/bounce_templates.c.
+20060807
+
+ Bugfix (introduced Postfix 2.2): when upgrading from Postfix
+ < 2.2 with the third-party TLS patch, the post-install
+ upgrade procedure didn't put a "?" in the existing tlsmgr
+ entry, causing tlsmgr to repeatedly start and exit when TLS
+ support was not compiled in. File: conf/post-install.
+
+20060812
+
+ Bugfix (introduced < Postfix alpha): safety mechanism in
+ mail_date() didn't work. Found in code review. File:
+ global/mail_date.c.
+
+20060817
+
+ Test programs for host address->name and name->address
+ lookups to debug name service inconsistencies, typically
+ when the Postfix SMTP server claims that a hostname is
+ "unknown". Files: auxiliary/name-addr-test/*.
+
+20060822
+
+ Added missing logging for "message to large" etc. Files:
+ smtpd/smtpd.c, cleanup/cleanup_milter.c.
+
+20060823
+
+ Bugfix (introduced Postfix 2.2): vstream_fdclose() did not
+ flush unwritten output before disconnecting a stream from
+ its file descriptor. File: util/vstream.c.
+
+ Bugfix (introduced Postfix 2.2): segfault when vstream_fclose()
+ attempted to flush unwritten output, after vstream_fdclose()
+ disconnected the stream from its file descriptor. File:
+ util/vstream.c.
+
+ Feature: smtp-sink can capture mail to file, either as one
+ individual message per file, or as multiple messages per
+ file. After an initial implementation by Weidong Cui. File:
+ smtpstone/smtp-sink.c.
+
+ Bugfix (introduced < Postfix alpha): smtp-sink did not
+ correctly recognize DOT-CR-LF immediately after DATA. File:
+ smtpstone/smtp-sink.c.
+
+ Cleanup: smtp-sink now requires that MAIL FROM, RCPT TO and
+ DATA be send in the correct order. This simplified the
+ implementation of the capture to file feature. File:
+ smtpstone/smtp-sink.c.
+
Wish list:
+ Make null local-part handling configurable: either expand
+ into mailer-daemon (current bahavior) or disallow (strict
+ behavior, currently implemented only in the SMTP server).
+
The type of var_message_limit should be changed from int
to long or better, to take advantage of LP64 architectures.
This also requires checking all expressions in which
"AUXLIBS=$CDB/cdb.a $CDB/alloc.a $CDB/buffer.a $CDB/unix.a $CDB/byte.a"
% make
-After postfix has been built with cdb support, you can use "cdb" tables
+After Postfix has been built with cdb support, you can use "cdb" tables
wherever you can use read-only "hash", "btree" or "dbm" tables. However, the
"p\bpo\bos\bst\btm\bma\bap\bp -\b-i\bi" (incremental record insertion) and "p\bpo\bos\bst\btm\bma\bap\bp -\b-d\bd" (incremental
record deletion) command-line options are not available. For the same reason
the "cdb" map type cannot be used to store the persistent address verification
-cache for the verify(8) service.
+cache for the verify(8) service, or to store TLS session information for the
+tlsmgr(8) service.
B\bBu\bui\bil\bld\bdi\bin\bng\bg P\bPo\bos\bst\btf\bfi\bix\bx w\bwi\bit\bth\bh D\bDo\bov\bve\bec\bco\bot\bt S\bSA\bAS\bSL\bL s\bsu\bup\bpp\bpo\bor\brt\bt
-Dovecot SASL support is available in Postfix 2.3 and later. The Dovecot source
-code is available via http://www.dovecot.org/. At the time of writing, only
-server-side SASL support is available, so you can't use it to authenticate to
-your network provider's server. Dovecot uses its own daemon process for
-authentication. This keeps the Postfix build process simple, because there is
-no need to link extra libraries into Postfix.
+Support for the Dovecot version 1 SASL protocol is available in Postfix 2.3 and
+later. At the time of writing, only server-side SASL support is available, so
+you can't use it to authenticate to your network provider's server. Dovecot
+uses its own daemon process for authentication. This keeps the Postfix build
+process simple, because there is no need to link extra libraries into Postfix.
To generate the necessary Makefiles, execute the following in the Postfix top-
level directory:
% make makefiles CCARGS='-DUSE_SASL_AUTH -
- DDEF_SASL_SERVER_TYPE=\"dovecot\"'
+ DDEF_SERVER_SASL_TYPE=\"dovecot\"'
After this, proceed with "make" as described in the INSTALL document.
Notes:
- * The "-DDEF_SASL_SERVER_TYPE" stuff is not necessary; it just makes Postfix
+ * The "-DDEF_SERVER_SASL_TYPE" stuff is not necessary; it just makes Postfix
configuration a little more convenient because you don't have to specify
the SASL plug-in type in the Postfix main.cf file.
--- /dev/null
+ /*
+ * getaddrinfo(3) (name->address lookup) tester.
+ *
+ * Compile with:
+ *
+ * cc -o getaddrinfo getaddrinfo.c (BSD, Linux)
+ *
+ * cc -o getaddrinfo getaddrinfo.c -lsocket -lnsl (SunOS 5.x)
+ *
+ * Run as: getaddrinfo hostname
+ *
+ * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+ *
+ * Author: Wietse Venema, IBM T.J. Watson Research, USA.
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+int main(int argc, char **argv)
+{
+ char hostbuf[NI_MAXHOST]; /* XXX */
+ struct addrinfo hints;
+ struct addrinfo *res0;
+ struct addrinfo *res;
+ const char *addr;
+ int err;
+
+#define NO_SERVICE ((char *) 0)
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s hostname\n", argv[0]);
+ exit(1);
+ }
+ memset((char *) &hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_flags = AI_CANONNAME;
+ hints.ai_socktype = SOCK_STREAM;
+ if ((err = getaddrinfo(argv[1], NO_SERVICE, &hints, &res0)) != 0) {
+ fprintf(stderr, "host %s not found\n", argv[1]);
+ exit(1);
+ }
+ printf("Hostname:\t%s\n", res0->ai_canonname);
+ printf("Addresses:\t");
+ for (res = res0; res != 0; res = res->ai_next) {
+ addr = (res->ai_family == AF_INET ?
+ (char *) &((struct sockaddr_in *) res->ai_addr)->sin_addr :
+ (char *) &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr);
+ if (inet_ntop(res->ai_family, addr, hostbuf, sizeof(hostbuf)) == 0) {
+ perror("inet_ntop:");
+ exit(1);
+ }
+ printf("%s ", hostbuf);
+ }
+ printf("\n");
+ freeaddrinfo(res0);
+ exit(0);
+}
--- /dev/null
+ /*
+ * gethostbyaddr tester. compile with:
+ *
+ * cc -o gethostbyaddr gethostbyaddr.c (SunOS 4.x)
+ *
+ * cc -o gethostbyaddr gethostbyaddr.c -lnsl (SunOS 5.x)
+ *
+ * run as: gethostbyaddr address
+ *
+ * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdio.h>
+
+main(argc, argv)
+int argc;
+char **argv;
+{
+ struct hostent *hp;
+ long addr;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s i.p.addres\n", argv[0]);
+ exit(1);
+ }
+ addr = inet_addr(argv[1]);
+ if (hp = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET)) {
+ printf("Hostname:\t%s\n", hp->h_name);
+ printf("Aliases:\t");
+ while (hp->h_aliases[0])
+ printf("%s ", *hp->h_aliases++);
+ printf("\n");
+ printf("Addresses:\t");
+ while (hp->h_addr_list[0])
+ printf("%s ", inet_ntoa(*(struct in_addr *) * hp->h_addr_list++));
+ printf("\n");
+ exit(0);
+ }
+ fprintf(stderr, "host %s not found\n", argv[1]);
+ exit(1);
+}
--- /dev/null
+ /*
+ * gethostbyname tester. compile with:
+ *
+ * cc -o gethostbyname gethostbyname.c (SunOS 4.x)
+ *
+ * cc -o gethostbyname gethostbyname.c -lnsl (SunOS 5.x)
+ *
+ * run as: gethostbyname hostname
+ *
+ * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdio.h>
+
+main(argc, argv)
+int argc;
+char **argv;
+{
+ struct hostent *hp;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s hostname\n", argv[0]);
+ exit(1);
+ }
+ if (hp = gethostbyname(argv[1])) {
+ printf("Hostname:\t%s\n", hp->h_name);
+ printf("Aliases:\t");
+ while (hp->h_aliases[0])
+ printf("%s ", *hp->h_aliases++);
+ printf("\n");
+ printf("Addresses:\t");
+ while (hp->h_addr_list[0])
+ printf("%s ", inet_ntoa(*(struct in_addr *) * hp->h_addr_list++));
+ printf("\n");
+ exit(0);
+ } else {
+ fprintf(stderr, "host %s not found\n", argv[1]);
+ exit(1);
+ }
+}
--- /dev/null
+ /*
+ * getnameinfo(3) (address->name lookup) tester.
+ *
+ * Compile with:
+ *
+ * cc -o getnameinfo getnameinfo.c (BSD, Linux)
+ *
+ * cc -o getnameinfo getnameinfo.c -lsocket -lnsl (SunOS 5.x)
+ *
+ * Run as: getnameinfo address
+ *
+ * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+ *
+ * Author: Wietse Venema, IBM T.J. Watson Research, USA.
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+int main(int argc, char **argv)
+{
+ char hostbuf[NI_MAXHOST]; /* XXX */
+ struct addrinfo hints;
+ struct addrinfo *res0;
+ struct addrinfo *res;
+ const char *host;
+ const char *addr;
+ int err;
+
+#define NO_SERVICE ((char *) 0)
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s ipaddres\n", argv[0]);
+ exit(1);
+ }
+
+ /*
+ * Convert address to internal form.
+ */
+ host = argv[1];
+ memset((char *) &hints, 0, sizeof(hints));
+ hints.ai_family = (strchr(host, ':') ? AF_INET6 : AF_INET);
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags |= AI_NUMERICHOST;
+ if ((err = getaddrinfo(host, NO_SERVICE, &hints, &res0)) != 0) {
+ fprintf(stderr, "getaddrinfo %s: %s\n", host, gai_strerror(err));
+ exit(1);
+ }
+
+ /*
+ * Convert host address to name.
+ */
+ for (res = res0; res != 0; res = res->ai_next) {
+ err = getnameinfo(res->ai_addr, res->ai_addrlen,
+ hostbuf, sizeof(hostbuf),
+ NO_SERVICE, 0, NI_NAMEREQD);
+ if (err) {
+ fprintf(stderr, "getnameinfo %s: %s\n", host, gai_strerror(err));
+ exit(1);
+ }
+ printf("Hostname:\t%s\n", hostbuf);
+ addr = (res->ai_family == AF_INET ?
+ (char *) &((struct sockaddr_in *) res->ai_addr)->sin_addr :
+ (char *) &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr);
+ if (inet_ntop(res->ai_family, addr, hostbuf, sizeof(hostbuf)) == 0) {
+ perror("inet_ntop:");
+ exit(1);
+ }
+ printf("Address:\t%s\n", hostbuf);
+ }
+ freeaddrinfo(res0);
+ exit(0);
+}
ed $config_directory/master.cf <<EOF || exit 1
/^tlsmgr[ ]*fifo[ ]/
s/fifo/unix/
+s/[0-9][0-9]*/&?/
p
w
q
</pre>
</blockquote>
-<p> After postfix has been built with cdb support, you can use
+<p> After Postfix has been built with cdb support, you can use
"cdb" tables wherever you can use read-only "hash", "btree" or
"dbm" tables. However, the "<b>postmap -i</b>" (incremental record
insertion) and "<b>postmap -d</b>" (incremental record deletion)
command-line options are not available. For the same reason the
"cdb" map type cannot be used to store the persistent address
-verification cache for the <a href="verify.8.html">verify(8)</a> service. </p>
+verification cache for the <a href="verify.8.html">verify(8)</a> service, or to store
+TLS session information for the <a href="tlsmgr.8.html">tlsmgr(8)</a> service. </p>
<h2><a name="build_dovecot">Building Postfix with Dovecot SASL
support</a></h2>
-<p> Dovecot SASL support is available in Postfix 2.3 and later. The
-Dovecot source code is available via <a href="http://www.dovecot.org/">http://www.dovecot.org/</a>. At
-the time
+<p> Support for the Dovecot version 1 SASL protocol is available
+in Postfix 2.3 and later. At the time
of writing, only server-side SASL support is available, so you can't
use it to authenticate to your network provider's server. Dovecot
uses its own daemon process for authentication. This keeps the
<blockquote>
<pre>
-% make makefiles CCARGS='-DUSE_SASL_AUTH -DDEF_SASL_SERVER_TYPE=\"dovecot\"'
+% make makefiles CCARGS='-DUSE_SASL_AUTH -DDEF_SERVER_SASL_TYPE=\"dovecot\"'
</pre>
</blockquote>
<ul>
-<li> <p> The "-DDEF_SASL_SERVER_TYPE" stuff is not necessary; it just
+<li> <p> The "-DDEF_SERVER_SASL_TYPE" stuff is not necessary; it just
makes Postfix configuration a little more convenient because you
don't have to specify the SASL plug-in type in the Postfix <a href="postconf.5.html">main.cf</a>
file. </p>
mented by connecting to the service and sending a
wake up request. A ? at the end of the wake-up
time field requests that no wake up events be sent
- before the service is used. Specify 0 for no auto-
- matic wake up.
+ before the first time a service is used. Specify 0
+ for no automatic wake up.
The <a href="pickup.8.html"><b>pickup</b>(8)</a>, <a href="qmgr.8.html"><b>qmgr</b>(8)</a> and <a href="flush.8.html"><b>flush</b>(8)</a> daemons require
a wake up timer.
error (broken mail software) reports.
</p>
+<p> NOTE: postmaster notifications may contain confidential information
+such as SASL passwords or message content. It is the system
+administrator's responsibility to treat such information with care.
+</p>
+
<p>
The error classes are:
</p>
<p>
The numerical Postfix SMTP server response code when a sender or
recipient address is rejected by the <a href="postconf.5.html#reject_unknown_sender_domain">reject_unknown_sender_domain</a>
-or <a href="postconf.5.html#reject_unknown_recipient_domain">reject_unknown_recipient_domain</a> restriction.
+or <a href="postconf.5.html#reject_unknown_recipient_domain">reject_unknown_recipient_domain</a> restriction. The response is
+always 450 in case of a temporary DNS error.
</p>
<p>
and IPv6 are the default. This program is the complement
of the <a href="qmqp-source.1.html"><b>qmqp-source</b>(1)</a> program.
+ Note: this is an unsupported test program. No attempt is
+ made to maintain compatibility between successive ver-
+ sions.
+
+ Arguments:
+
<b>-4</b> Support IPv4 only. This option has no effect when
Postfix is built without IPv6 support.
protocol. Connections can be made to UNIX-domain and IPv4
or IPv6 servers. IPv4 and IPv6 are the default.
- Options:
+ Note: this is an unsupported test program. No attempt is
+ made to maintain compatibility between successive ver-
+ sions.
+
+ Arguments:
<b>-4</b> Connect to the server with IPv4. This option has no
effect when Postfix is built without IPv6 support.
away. The purpose is to measure client performance, not
protocol compliance.
+ <b>smtp-sink</b> may also be configured to capture each mail
+ delivery transaction to file. Since disk latencies are
+ large compared to network delays, this mode of operation
+ can reduce the maximal performance by several orders of
+ magnitude.
+
Connections can be accepted on IPv4 or IPv6 endpoints, or
on UNIX-domain sockets. IPv4 and IPv6 are the default.
This program is the complement of the <a href="smtp-source.1.html"><b>smtp-source</b>(1)</a> pro-
gram.
+ Note: this is an unsupported test program. No attempt is
+ made to maintain compatibility between successive ver-
+ sions.
+
Arguments:
<b>-4</b> Support IPv4 only. This option has no effect when
<b>-C</b> Disable XCLIENT support.
+ <b>-d</b> <i>dump-template</i>
+ Dump each mail transaction to a single-message file
+ whose name is created by expanding the <i>dump-tem-</i>
+ <i>plate</i> via strftime(3) and appending a pseudo-random
+ hexadecimal number (example: "%Y%m%d%H/%M." expands
+ into "2006081203/05.809a62e3"). If the template
+ contains "/" characters, missing directories are
+ created automatically. The message dump format is
+ described below.
+
+ Note: this option keeps one capture file open for
+ every mail transaction in progress.
+
+ <b>-D</b> <i>dump-template</i>
+ Append mail transactions to a multi-message dump
+ file whose name is created by expanding the <i>dump-</i>
+ <i>template</i> via strftime(3). If the template contains
+ "/" characters, missing directories are created
+ automatically. The message dump format is
+ described below.
+
+ Note: this option keeps one capture file open for
+ every mail transaction in progress.
+
<b>-e</b> Do not announce ESMTP support.
<b>-E</b> Do not announce ENHANCEDSTATUSCODES support.
<b>-L</b> Enable LMTP instead of SMTP.
+ <b>-m</b> <i>count</i> (default: 256)
+ An upper bound on the maximal number of simultane-
+ ous connections that <b>smtp-sink</b> will handle. This
+ prevents the process from running out of file
+ descriptors. Excess connections will stay queued in
+ the TCP/IP stack.
+
<b>-n</b> <i>count</i>
Terminate after <i>count</i> sessions. This is for testing
purposes.
and use quotes to protect white space from the
shell. Command names are case-insensitive.
+ <b>-S start-string</b>
+ An optional string that is prepended to each mes-
+ sage that is written to a dump file (see the dump
+ file format description below). The following C
+ escape sequences are supported: \a (bell), \b
+ (backslace), \f (formfeed), \n (newline), \r (car-
+ riage return), \t (horizontal tab), \v (vertical
+ tab), \<i>ddd</i> (up to three octal digits) and \\ (the
+ backslash character).
+
<b>-t</b> <i>timeout</i> (default: 100)
Limit the time for receiving a command or sending a
- response. The time limit is specified in seconds.
+ response. The time limit is specified in seconds.
<b>-v</b> Show the SMTP conversations.
mand.
[<b>inet:</b>][<i>host</i>]:<i>port</i>
- Listen on network interface <i>host</i> (default: any
+ Listen on network interface <i>host</i> (default: any
interface) TCP port <i>port</i>. Both <i>host</i> and <i>port</i> may be
specified in numeric or symbolic form.
Listen on the UNIX-domain socket at <i>pathname</i>.
<i>backlog</i>
- The maximum length the queue of pending connec-
+ The maximum length the queue of pending connec-
tions, as defined by the <b>listen</b>(2) system call.
+<b>DUMP FILE FORMAT</b>
+ Each dumped message contains a sequence of text lines,
+ terminated with the newline character. The sequence of
+ information is as follows:
+
+ <b>o</b> The optional string specified with the <b>-S</b> option.
+
+ <b>o</b> The <b>smtp-sink</b> generated headers as documented
+ below.
+
+ <b>o</b> The message header and body as received from the
+ SMTP client.
+
+ <b>o</b> An empty line.
+
+ The format of the <b>smtp-sink</b> generated headers is as fol-
+ lows:
+
+ <b>X-Client-Addr:</b> <i>text</i>
+ The client IP address without enclosing []. An IPv6
+ address is prefixed with "ipv6:". This record is
+ always present.
+
+ <b>X-Client-Proto:</b> <i>text</i>
+ The client protocol: SMTP, ESMTP or LMTP. This
+ record is always present.
+
+ <b>X-Helo-Args:</b> <i>text</i>
+ The arguments of the last HELO or EHLO command
+ before this mail delivery transaction. This record
+ is present only if the client sent a recognizable
+ HELO or EHLO command before the DATA command.
+
+ <b>X-Mail-Args:</b> <i>text</i>
+ The arguments, if any, of the MAIL command that
+ started this mail delivery transaction. This record
+ is present only if the client sent a recognizable
+ MAIL command.
+
+ <b>X-Rcpt-Args:</b> <i>text</i>
+ The arguments, if any, of each successive RCPT com-
+ mand within this mail delivery transaction. There
+ may be zero or more of these records. This record
+ is present only if the client sent a recognizable
+ RCPT command.
+
+ <b>Received:</b> <i>text</i>
+ A message header for compatibility with mail pro-
+ cessing software. This three-line header marks the
+ end of the headers provided by <b>smtp-sink</b>, and is
+ formatted as follows:
+
+ <b>from</b> <i>helo</i> <b>([</b><i>addr</i><b>])</b>
+ The HELO or EHLO command argument and client
+ IP address. If the client did not send HELO
+ or EHLO, the client IP address is used
+ instead.
+
+ <b>by</b> <i>host</i> <b>(smtp-sink) with</b> <i>proto</i> <b>id</b> <i>random</i><b>;</b>
+ The hostname specified with the <b>-h</b> option,
+ the client protocol (see <b>X-Client-Proto</b>
+ above), and the pseudo-random portion of the
+ per-message capture file name.
+
+ <i>time-stamp</i>
+ A time stamp as defined in <a href="http://www.faqs.org/rfcs/rfc2822.html">RFC 2822</a>.
+
<b>SEE ALSO</b>
<a href="smtp-source.1.html">smtp-source(1)</a>, SMTP/LMTP message generator
UNIX-domain and IPv4 or IPv6 servers. IPv4 and IPv6 are
the default.
+ Note: this is an unsupported test program. No attempt is
+ made to maintain compatibility between successive ver-
+ sions.
+
Arguments:
<b>-4</b> Connect to the server with IPv4. This option has no
UNIX-domain sockets.
IPv4 and IPv6 are the default.
This program is the complement of the \fBqmqp-source\fR(1) program.
+
+Note: this is an unsupported test program. No attempt is made
+to maintain compatibility between successive versions.
+
+Arguments:
.IP \fB-4\fR
Support IPv4 only. This option has no effect when
Postfix is built without IPv6 support.
Connections can be made to UNIX-domain and IPv4 or IPv6 servers.
IPv4 and IPv6 are the default.
-Options:
+Note: this is an unsupported test program. No attempt is made
+to maintain compatibility between successive versions.
+
+Arguments:
.IP \fB-4\fR
Connect to the server with IPv4. This option has no effect when
Postfix is built without IPv6 support.
The purpose is to measure client performance, not protocol
compliance.
+\fBsmtp-sink\fR may also be configured to capture each mail
+delivery transaction to file. Since disk latencies are large
+compared to network delays, this mode of operation can
+reduce the maximal performance by several orders of magnitude.
+
Connections can be accepted on IPv4 or IPv6 endpoints, or on
UNIX-domain sockets.
IPv4 and IPv6 are the default.
This program is the complement of the \fBsmtp-source\fR(1) program.
+Note: this is an unsupported test program. No attempt is made
+to maintain compatibility between successive versions.
+
Arguments:
.IP \fB-4\fR
Support IPv4 only. This option has no effect when
received.
.IP \fB-C\fR
Disable XCLIENT support.
+.IP "\fB-d \fIdump-template\fR"
+Dump each mail transaction to a single-message file whose
+name is created by expanding the \fIdump-template\fR via
+strftime(3) and appending a pseudo-random hexadecimal number
+(example: "%Y%m%d%H/%M." expands into "2006081203/05.809a62e3").
+If the template contains "/" characters, missing directories
+are created automatically. The message dump format is
+described below.
+.sp
+Note: this option keeps one capture file open for every
+mail transaction in progress.
+.IP "\fB-D \fIdump-template\fR"
+Append mail transactions to a multi-message dump file whose
+name is created by expanding the \fIdump-template\fR via
+strftime(3).
+If the template contains "/" characters, missing directories
+are created automatically. The message dump format is
+described below.
+.sp
+Note: this option keeps one capture file open for every
+mail transaction in progress.
.IP \fB-e\fR
Do not announce ESMTP support.
.IP \fB-E\fR
and in the EHLO response. The default hostname is "smtp-sink".
.IP \fB-L\fR
Enable LMTP instead of SMTP.
+.IP "\fB-m \fIcount\fR (default: 256)"
+An upper bound on the maximal number of simultaneous
+connections that \fBsmtp-sink\fR will handle. This prevents
+the process from running out of file descriptors. Excess
+connections will stay queued in the TCP/IP stack.
.IP "\fB-n \fIcount\fR"
Terminate after \fIcount\fR sessions. This is for testing purposes.
.IP \fB-p\fR
DATA, ., RSET, NOOP, and QUIT. Separate command names by
white space or commas, and use quotes to protect white space
from the shell. Command names are case-insensitive.
+.IP "\fB-S start-string\fR"
+An optional string that is prepended to each message that is
+written to a dump file (see the dump file format description
+below). The following C escape sequences are supported: \ea
+(bell), \eb (backslace), \ef (formfeed), \en (newline), \er
+(carriage return), \et (horizontal tab), \ev (vertical tab),
+\e\fIddd\fR (up to three octal digits) and \e\e (the backslash
+character).
.IP "\fB-t \fItimeout\fR (default: 100)"
Limit the time for receiving a command or sending a response.
The time limit is specified in seconds.
.IP \fIbacklog\fR
The maximum length the queue of pending connections,
as defined by the \fBlisten\fR(2) system call.
+.SH "DUMP FILE FORMAT"
+.na
+.nf
+.ad
+.fi
+Each dumped message contains a sequence of text lines,
+terminated with the newline character. The sequence of
+information is as follows:
+.IP \(bu
+The optional string specified with the \fB-S\fR option.
+.IP \(bu
+The \fBsmtp-sink\fR generated headers as documented below.
+.IP \(bu
+The message header and body as received from the SMTP client.
+.IP \(bu
+An empty line.
+.PP
+The format of the \fBsmtp-sink\fR generated headers is as
+follows:
+.IP "\fBX-Client-Addr: \fItext\fR"
+The client IP address without enclosing []. An IPv6 address
+is prefixed with "ipv6:". This record is always present.
+.IP "\fBX-Client-Proto: \fItext\fR"
+The client protocol: SMTP, ESMTP or LMTP. This record is
+always present.
+.IP "\fBX-Helo-Args: \fItext\fR"
+The arguments of the last HELO or EHLO command before this
+mail delivery transaction. This record is present only if
+the client sent a recognizable HELO or EHLO command before
+the DATA command.
+.IP "\fBX-Mail-Args: \fItext\fR"
+The arguments, if any, of the MAIL command that started
+this mail delivery transaction. This record is present only
+if the client sent a recognizable MAIL command.
+.IP "\fBX-Rcpt-Args: \fItext\fR"
+The arguments, if any, of each successive RCPT command
+within this mail delivery transaction. There may be zero
+or more of these records. This record is present only if
+the client sent a recognizable RCPT command.
+.IP "\fBReceived: \fItext\fR"
+A message header for compatibility with mail processing
+software. This three-line header marks the end of the headers
+provided by \fBsmtp-sink\fR, and is formatted
+as follows:
+.RS
+.IP "\fBfrom \fIhelo\fB ([\fIaddr\fB])\fR"
+The HELO or EHLO command argument and client IP address.
+If the client did not send HELO or EHLO, the client IP
+address is used instead.
+.IP "\fBby \fIhost\fB (smtp-sink) with \fIproto\fB id \fIrandom\fB;\fR"
+The hostname specified with the \fB-h\fR option, the client
+protocol (see \fBX-Client-Proto\fR above), and the pseudo-random
+portion of the per-message capture file name.
+.IP \fItime-stamp\fR
+A time stamp as defined in RFC 2822.
+.RE
.SH "SEE ALSO"
.na
.nf
Connections can be made to UNIX-domain and IPv4 or IPv6 servers.
IPv4 and IPv6 are the default.
+Note: this is an unsupported test program. No attempt is made
+to maintain compatibility between successive versions.
+
Arguments:
.IP \fB-4\fR
Connect to the server with IPv4. This option has no effect when
number of seconds. The wake up is implemented by connecting
to the service and sending a wake up request. A ? at the
end of the wake-up time field requests that no wake up
-events be sent before the service is used.
+events be sent before the first time a service is used.
Specify 0 for no automatic wake up.
.sp
The \fBpickup\fR(8), \fBqmgr\fR(8) and \fBflush\fR(8)
may wish to turn on the policy (UCE and mail relaying) and protocol
error (broken mail software) reports.
.PP
+NOTE: postmaster notifications may contain confidential information
+such as SASL passwords or message content. It is the system
+administrator's responsibility to treat such information with care.
+.PP
The error classes are:
.IP "\fBbounce\fR (also implies \fB2bounce\fR)"
Send the postmaster copies of the headers of bounced mail, and
.SH unknown_address_reject_code (default: 450)
The numerical Postfix SMTP server response code when a sender or
recipient address is rejected by the reject_unknown_sender_domain
-or reject_unknown_recipient_domain restriction.
+or reject_unknown_recipient_domain restriction. The response is
+always 450 in case of a temporary DNS error.
.PP
Do not change this unless you have a complete understanding of RFC 821.
.SH unknown_client_reject_code (default: 450)
</pre>
</blockquote>
-<p> After postfix has been built with cdb support, you can use
+<p> After Postfix has been built with cdb support, you can use
"cdb" tables wherever you can use read-only "hash", "btree" or
"dbm" tables. However, the "<b>postmap -i</b>" (incremental record
insertion) and "<b>postmap -d</b>" (incremental record deletion)
command-line options are not available. For the same reason the
"cdb" map type cannot be used to store the persistent address
-verification cache for the verify(8) service. </p>
+verification cache for the verify(8) service, or to store
+TLS session information for the tlsmgr(8) service. </p>
<h2><a name="build_dovecot">Building Postfix with Dovecot SASL
support</a></h2>
-<p> Dovecot SASL support is available in Postfix 2.3 and later. The
-Dovecot source code is available via http://www.dovecot.org/. At
-the time
+<p> Support for the Dovecot version 1 SASL protocol is available
+in Postfix 2.3 and later. At the time
of writing, only server-side SASL support is available, so you can't
use it to authenticate to your network provider's server. Dovecot
uses its own daemon process for authentication. This keeps the
<blockquote>
<pre>
-% make makefiles CCARGS='-DUSE_SASL_AUTH -DDEF_SASL_SERVER_TYPE=\"dovecot\"'
+% make makefiles CCARGS='-DUSE_SASL_AUTH -DDEF_SERVER_SASL_TYPE=\"dovecot\"'
</pre>
</blockquote>
<ul>
-<li> <p> The "-DDEF_SASL_SERVER_TYPE" stuff is not necessary; it just
+<li> <p> The "-DDEF_SERVER_SASL_TYPE" stuff is not necessary; it just
makes Postfix configuration a little more convenient because you
don't have to specify the SASL plug-in type in the Postfix main.cf
file. </p>
# number of seconds. The wake up is implemented by connecting
# to the service and sending a wake up request. A ? at the
# end of the wake-up time field requests that no wake up
-# events be sent before the service is used.
+# events be sent before the first time a service is used.
# Specify 0 for no automatic wake up.
# .sp
# The \fBpickup\fR(8), \fBqmgr\fR(8) and \fBflush\fR(8)
error (broken mail software) reports.
</p>
+<p> NOTE: postmaster notifications may contain confidential information
+such as SASL passwords or message content. It is the system
+administrator's responsibility to treat such information with care.
+</p>
+
<p>
The error classes are:
</p>
<p>
The numerical Postfix SMTP server response code when a sender or
recipient address is rejected by the reject_unknown_sender_domain
-or reject_unknown_recipient_domain restriction.
+or reject_unknown_recipient_domain restriction. The response is
+always 450 in case of a temporary DNS error.
</p>
<p>
static void cleanup_milter_set_error(CLEANUP_STATE *state, int err)
{
- if (err == EFBIG)
+ if (err == EFBIG) {
+ msg_warn("%s: queue file size limit exceeded", state->queue_id);
state->errs |= CLEANUP_STAT_SIZE;
- else
+ } else {
+ msg_warn("%s: write queue file: %m", state->queue_id);
state->errs |= CLEANUP_STAT_WRITE;
+ }
}
/* cleanup_milter_error - return dummy error description */
* Finally, add the time zone name.
*/
while (strftime(vstring_end(vp), vstring_avail(vp), " (%Z)", lt) == 0)
- VSTRING_SPACE(vp, 100);
+ VSTRING_SPACE(vp, vstring_avail(vp) + 100);
VSTRING_SKIP(vp);
return (vstring_str(vp));
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20060806"
+#define MAIL_RELEASE_DATE "20060823"
#define MAIL_VERSION_NUMBER "2.4"
#ifdef SNAPSHOT
}
vstring_strcpy(state->dsn_orcpt_buf, arg + 6);
if (dsn_orcpt_addr
- || (coded_addr = split_at(STR(state->dsn_orcpt_buf), ';')) == 0
+ || (coded_addr = split_at(STR(state->dsn_orcpt_buf), ';')) == 0
|| xtext_unquote(state->dsn_buf, coded_addr) == 0
|| *(dsn_orcpt_type = STR(state->dsn_orcpt_buf)) == 0) {
state->error_mask |= MAIL_ERROR_PROTOCOL;
&& (state->proxy == 0 ? (++start, --len) == 0 : len == 1))
break;
if (state->err == CLEANUP_STAT_OK) {
- if (var_message_limit > 0 && var_message_limit - state->act_size < len + 2)
+ if (var_message_limit > 0 && var_message_limit - state->act_size < len + 2) {
state->err = CLEANUP_STAT_SIZE;
- else {
+ msg_warn("%s: queue file size limit exceeded",
+ state->queue_id ? state->queue_id : "NOQUEUE");
+ } else {
state->act_size += len + 2;
if (out_record(out_stream, curr_rec_type, start, len) < 0)
state->err = out_error;
/* UNIX-domain sockets.
/* IPv4 and IPv6 are the default.
/* This program is the complement of the \fBqmqp-source\fR(1) program.
+/*
+/* Note: this is an unsupported test program. No attempt is made
+/* to maintain compatibility between successive versions.
+/*
+/* Arguments:
/* .IP \fB-4\fR
/* Support IPv4 only. This option has no effect when
/* Postfix is built without IPv6 support.
/* Connections can be made to UNIX-domain and IPv4 or IPv6 servers.
/* IPv4 and IPv6 are the default.
/*
-/* Options:
+/* Note: this is an unsupported test program. No attempt is made
+/* to maintain compatibility between successive versions.
+/*
+/* Arguments:
/* .IP \fB-4\fR
/* Connect to the server with IPv4. This option has no effect when
/* Postfix is built without IPv6 support.
/* The purpose is to measure client performance, not protocol
/* compliance.
/*
+/* \fBsmtp-sink\fR may also be configured to capture each mail
+/* delivery transaction to file. Since disk latencies are large
+/* compared to network delays, this mode of operation can
+/* reduce the maximal performance by several orders of magnitude.
+/*
/* Connections can be accepted on IPv4 or IPv6 endpoints, or on
/* UNIX-domain sockets.
/* IPv4 and IPv6 are the default.
/* This program is the complement of the \fBsmtp-source\fR(1) program.
/*
+/* Note: this is an unsupported test program. No attempt is made
+/* to maintain compatibility between successive versions.
+/*
/* Arguments:
/* .IP \fB-4\fR
/* Support IPv4 only. This option has no effect when
/* received.
/* .IP \fB-C\fR
/* Disable XCLIENT support.
+/* .IP "\fB-d \fIdump-template\fR"
+/* Dump each mail transaction to a single-message file whose
+/* name is created by expanding the \fIdump-template\fR via
+/* strftime(3) and appending a pseudo-random hexadecimal number
+/* (example: "%Y%m%d%H/%M." expands into "2006081203/05.809a62e3").
+/* If the template contains "/" characters, missing directories
+/* are created automatically. The message dump format is
+/* described below.
+/* .sp
+/* Note: this option keeps one capture file open for every
+/* mail transaction in progress.
+/* .IP "\fB-D \fIdump-template\fR"
+/* Append mail transactions to a multi-message dump file whose
+/* name is created by expanding the \fIdump-template\fR via
+/* strftime(3).
+/* If the template contains "/" characters, missing directories
+/* are created automatically. The message dump format is
+/* described below.
+/* .sp
+/* Note: this option keeps one capture file open for every
+/* mail transaction in progress.
/* .IP \fB-e\fR
/* Do not announce ESMTP support.
/* .IP \fB-E\fR
/* and in the EHLO response. The default hostname is "smtp-sink".
/* .IP \fB-L\fR
/* Enable LMTP instead of SMTP.
+/* .IP "\fB-m \fIcount\fR (default: 256)"
+/* An upper bound on the maximal number of simultaneous
+/* connections that \fBsmtp-sink\fR will handle. This prevents
+/* the process from running out of file descriptors. Excess
+/* connections will stay queued in the TCP/IP stack.
/* .IP "\fB-n \fIcount\fR"
/* Terminate after \fIcount\fR sessions. This is for testing purposes.
/* .IP \fB-p\fR
/* DATA, ., RSET, NOOP, and QUIT. Separate command names by
/* white space or commas, and use quotes to protect white space
/* from the shell. Command names are case-insensitive.
+/* .IP "\fB-S start-string\fR"
+/* An optional string that is prepended to each message that is
+/* written to a dump file (see the dump file format description
+/* below). The following C escape sequences are supported: \ea
+/* (bell), \eb (backslace), \ef (formfeed), \en (newline), \er
+/* (carriage return), \et (horizontal tab), \ev (vertical tab),
+/* \e\fIddd\fR (up to three octal digits) and \e\e (the backslash
+/* character).
/* .IP "\fB-t \fItimeout\fR (default: 100)"
/* Limit the time for receiving a command or sending a response.
/* The time limit is specified in seconds.
/* .IP \fIbacklog\fR
/* The maximum length the queue of pending connections,
/* as defined by the \fBlisten\fR(2) system call.
+/* DUMP FILE FORMAT
+/* .ad
+/* .fi
+/* Each dumped message contains a sequence of text lines,
+/* terminated with the newline character. The sequence of
+/* information is as follows:
+/* .IP \(bu
+/* The optional string specified with the \fB-S\fR option.
+/* .IP \(bu
+/* The \fBsmtp-sink\fR generated headers as documented below.
+/* .IP \(bu
+/* The message header and body as received from the SMTP client.
+/* .IP \(bu
+/* An empty line.
+/* .PP
+/* The format of the \fBsmtp-sink\fR generated headers is as
+/* follows:
+/* .IP "\fBX-Client-Addr: \fItext\fR"
+/* The client IP address without enclosing []. An IPv6 address
+/* is prefixed with "ipv6:". This record is always present.
+/* .IP "\fBX-Client-Proto: \fItext\fR"
+/* The client protocol: SMTP, ESMTP or LMTP. This record is
+/* always present.
+/* .IP "\fBX-Helo-Args: \fItext\fR"
+/* The arguments of the last HELO or EHLO command before this
+/* mail delivery transaction. This record is present only if
+/* the client sent a recognizable HELO or EHLO command before
+/* the DATA command.
+/* .IP "\fBX-Mail-Args: \fItext\fR"
+/* The arguments, if any, of the MAIL command that started
+/* this mail delivery transaction. This record is present only
+/* if the client sent a recognizable MAIL command.
+/* .IP "\fBX-Rcpt-Args: \fItext\fR"
+/* The arguments, if any, of each successive RCPT command
+/* within this mail delivery transaction. There may be zero
+/* or more of these records. This record is present only if
+/* the client sent a recognizable RCPT command.
+/* .IP "\fBReceived: \fItext\fR"
+/* A message header for compatibility with mail processing
+/* software. This three-line header marks the end of the headers
+/* provided by \fBsmtp-sink\fR, and is formatted
+/* as follows:
+/* .RS
+/* .IP "\fBfrom \fIhelo\fB ([\fIaddr\fB])\fR"
+/* The HELO or EHLO command argument and client IP address.
+/* If the client did not send HELO or EHLO, the client IP
+/* address is used instead.
+/* .IP "\fBby \fIhost\fB (smtp-sink) with \fIproto\fB id \fIrandom\fB;\fR"
+/* The hostname specified with the \fB-h\fR option, the client
+/* protocol (see \fBX-Client-Proto\fR above), and the pseudo-random
+/* portion of the per-message capture file name.
+/* .IP \fItime-stamp\fR
+/* A time stamp as defined in RFC 2822.
+/* .RE
/* SEE ALSO
/* smtp-source(1), SMTP/LMTP message generator
/* LICENSE
#include <sys_defs.h>
#include <sys/socket.h>
#include <sys/wait.h>
+#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <syslog.h>
#include <signal.h>
+#include <time.h>
+#include <ctype.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#include <stringops.h>
#include <sane_accept.h>
#include <inet_proto.h>
+#include <myaddrinfo.h>
+#include <make_dirs.h>
+#include <myrand.h>
/* Global library. */
#include <smtp_stream.h>
+#include <mail_date.h>
/* Application-specific. */
VSTRING *buffer;
int data_state;
int (*read_fn) (struct SINK_STATE *);
+ int in_mail;
int rcpts;
char *push_back_ptr;
+ /* Capture file information for fake Received: header */
+ MAI_HOSTADDR_STR client_addr; /* IP address */
+ char *addr_prefix; /* ipv6: or empty */
+ char *helo_args; /* text after HELO or EHLO */
+ const char *client_proto; /* SMTP, ESMTP, LMTP */
+ time_t start_time; /* MAIL command time */
+ int id; /* pseudo-random */
+ VSTREAM *dump_file; /* dump file or null */
} SINK_STATE;
#define ST_ANY 0
#define PUSH_BACK_GET(state) (*(state)->push_back_ptr++)
#define PUSH_BACK_SET(state, text) ((state)->push_back_ptr = (text))
+#ifndef DEF_MAX_CLIENT_COUNT
+#define DEF_MAX_CLIENT_COUNT 256
+#endif
+
static int var_tmout = 100;
static int var_max_line_length = 2048;
static char *var_myhostname;
static int disable_xclient;
static int disable_xforward;
static int disable_enh_status;
+static int max_client_count = DEF_MAX_CLIENT_COUNT;
+static int client_count;
+static int sock;
+
+static char *single_template; /* individual template */
+static char *shared_template; /* shared template */
+static VSTRING *start_string; /* dump content prefix */
#define SOFT_ERROR_RESP "450 4.3.0 Error: command failed"
#define HARD_ERROR_RESP "500 5.3.0 Error: command failed"
+#define STR(x) vstring_str(x)
+
/* do_stats - show counters */
static void do_stats(void)
smtp_flush(state->stream);
}
+/* exp_path_template - expand template pathname, static result */
+
+static VSTRING *exp_path_template(const char *template, time_t start_time)
+{
+ static VSTRING *path_buf = 0;
+ struct tm *lt;
+
+ if (path_buf == 0)
+ path_buf = vstring_alloc(100);
+ else
+ VSTRING_RESET(path_buf);
+ lt = localtime(&start_time);
+ while (strftime(STR(path_buf), vstring_avail(path_buf), template, lt) == 0)
+ VSTRING_SPACE(path_buf, vstring_avail(path_buf) + 100);
+ VSTRING_SKIP(path_buf);
+ return (path_buf);
+}
+
+/* make_parent_dir - create parent directory or bust */
+
+static void make_parent_dir(const char *path, mode_t mode)
+{
+ const char *parent;
+
+ parent = sane_dirname((VSTRING *) 0, path);
+ if (make_dirs(parent, mode) < 0)
+ msg_fatal("mkdir %s: %m", parent);
+}
+
+/* mail_file_open - open mail capture file */
+
+static void mail_file_open(SINK_STATE *state)
+{
+ const char *myname = "mail_file_open";
+ VSTRING *path_buf;
+ ssize_t len;
+ int tries = 0;
+
+ /*
+ * Save the start time for later.
+ */
+ time(&(state->start_time));
+
+ /*
+ * Expand the per-message dumpfile pathname template.
+ */
+ path_buf = exp_path_template(single_template, state->start_time);
+
+ /*
+ * Append a random hexadecimal string to the pathname and create a new
+ * file. Retry with a different path if the file already exists. Create
+ * intermediate directories on the fly when the template specifies
+ * multiple pathname segments.
+ */
+#define ID_FORMAT "%08x"
+
+ for (len = VSTRING_LEN(path_buf); /* void */ ; vstring_truncate(path_buf, len)) {
+ if (++tries > 100)
+ msg_fatal("%s: something is looping", myname);
+ state->id = myrand();
+ vstring_sprintf_append(path_buf, ID_FORMAT, state->id);
+ if ((state->dump_file = vstream_fopen(STR(path_buf),
+ O_RDWR | O_CREAT | O_EXCL,
+ 0644)) != 0) {
+ break;
+ } else if (errno == EEXIST) {
+ continue;
+ } else if (errno == ENOENT) {
+ make_parent_dir(STR(path_buf), 0755);
+ continue;
+ } else {
+ msg_fatal("open %s: %m", STR(path_buf));
+ }
+ }
+
+ /*
+ * Don't leave temporary files behind.
+ */
+ if (shared_template != 0 && unlink(STR(path_buf)) < 0)
+ msg_fatal("unlink %s: %m", STR(path_buf));
+
+ /*
+ * Do initial header records.
+ */
+ if (start_string)
+ vstream_fprintf(state->dump_file, "%s", STR(start_string));
+ vstream_fprintf(state->dump_file, "X-Client-Addr: %s%s\n",
+ state->addr_prefix, state->client_addr.buf);
+ vstream_fprintf(state->dump_file, "X-Client-Proto: %s\n", state->client_proto);
+ if (state->helo_args)
+ vstream_fprintf(state->dump_file, "X-Helo-Args: %s\n", state->helo_args);
+ /* Note: there may be more than one recipient. */
+}
+
+/* mail_file_finish_header - do final smtp-sink generated header records */
+
+static void mail_file_finish_header(SINK_STATE *state)
+{
+ if (state->helo_args)
+ vstream_fprintf(state->dump_file, "Received: from %s ([%s%s])\n",
+ state->helo_args, state->addr_prefix,
+ state->client_addr.buf);
+ else
+ vstream_fprintf(state->dump_file, "Received: from [%s%s] ([%s%s])\n",
+ state->addr_prefix, state->client_addr.buf,
+ state->addr_prefix, state->client_addr.buf);
+ vstream_fprintf(state->dump_file, "\tby %s (smtp-sink)"
+ " with %s id " ID_FORMAT ";\n",
+ var_myhostname, state->client_proto, state->id);
+ vstream_fprintf(state->dump_file, "\t%s\n", mail_date(state->start_time));
+}
+
+/* mail_file_cleanup - common cleanup for capture file */
+
+static void mail_file_cleanup(SINK_STATE *state)
+{
+ (void) vstream_fclose(state->dump_file);
+ state->dump_file = 0;
+}
+
+/* mail_file_finish - handle message completion for capture file */
+
+static void mail_file_finish(SINK_STATE *state)
+{
+
+ /*
+ * Optionally append the captured message to a shared dumpfile.
+ */
+ if (shared_template) {
+ const char *out_path;
+ VSTREAM *out_fp;
+ ssize_t count;
+
+ /*
+ * Expand the shared dumpfile pathname template.
+ */
+ out_path = STR(exp_path_template(shared_template, state->start_time));
+
+ /*
+ * Open the shared dump file.
+ */
+#define OUT_OPEN_FLAGS (O_WRONLY | O_CREAT | O_APPEND)
+#define OUT_OPEN_MODE 0644
+
+ if ((out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE))
+ == 0 && errno == ENOENT) {
+ make_parent_dir(out_path, 0755);
+ out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE);
+ }
+ if (out_fp == 0)
+ msg_fatal("open %s: %m", out_path);
+
+ /*
+ * Append message content from single-message dump file.
+ */
+ if (vstream_fseek(state->dump_file, 0L, SEEK_SET) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(state->dump_file));
+ VSTRING_RESET(state->buffer);
+ for (;;) {
+ count = vstream_fread(state->dump_file, STR(state->buffer),
+ vstring_avail(state->buffer));
+ if (count <= 0)
+ break;
+ if (vstream_fwrite(out_fp, STR(state->buffer), count) != count)
+ msg_fatal("append file %s: %m", out_path);
+ }
+ if (vstream_ferror(state->dump_file))
+ msg_fatal("read file %s: %m", VSTREAM_PATH(state->dump_file));
+ if (vstream_fclose(out_fp))
+ msg_fatal("append file %s: %m", out_path);
+ }
+ mail_file_cleanup(state);
+}
+
+/* mail_file_reset - abort mail to capture file */
+
+static void mail_file_reset(SINK_STATE *state)
+{
+ if (shared_template == 0
+ && unlink(VSTREAM_PATH(state->dump_file)) < 0
+ && errno != ENOENT)
+ msg_fatal("unlink %s: %m", VSTREAM_PATH(state->dump_file));
+ mail_file_cleanup(state);
+}
+
+/* mail_cmd_reset - reset mail transaction information */
+
+static void mail_cmd_reset(SINK_STATE *state)
+{
+ state->in_mail = 0;
+ /* Not: state->rcpts = 0. This breaks the DOT reply with LMTP. */
+ if (state->dump_file)
+ mail_file_reset(state);
+}
+
/* ehlo_response - respond to EHLO command */
-static void ehlo_response(SINK_STATE *state)
+static void ehlo_response(SINK_STATE *state, const char *args)
{
+#define SKIP(cp, cond) for (/* void */; *cp && (cond); cp++)
+
+ /* EHLO aborts a mail transaction in progress. */
+ mail_cmd_reset(state);
+ if (enable_lmtp == 0)
+ state->client_proto = "ESMTP";
smtp_printf(state->stream, "250-%s", var_myhostname);
if (!disable_pipelining)
smtp_printf(state->stream, "250-PIPELINING");
smtp_printf(state->stream, "250-ENHANCEDSTATUSCODES");
smtp_printf(state->stream, "250 ");
smtp_flush(state->stream);
+ if (single_template) {
+ if (state->helo_args)
+ myfree(state->helo_args);
+ SKIP(args, ISSPACE(*args));
+ state->helo_args = mystrdup(args);
+ }
}
/* helo_response - respond to HELO command */
-static void helo_response(SINK_STATE *state)
+static void helo_response(SINK_STATE *state, const char *args)
{
+ /* HELO aborts a mail transaction in progress. */
+ mail_cmd_reset(state);
+ state->client_proto = "SMTP";
smtp_printf(state->stream, "250 %s", var_myhostname);
smtp_flush(state->stream);
+ if (single_template) {
+ if (state->helo_args)
+ myfree(state->helo_args);
+ SKIP(args, ISSPACE(*args));
+ state->helo_args = mystrdup(args);
+ }
}
/* ok_response - send 250 OK */
-static void ok_response(SINK_STATE *state)
+static void ok_response(SINK_STATE *state, const char *unused_args)
{
smtp_printf(state->stream, "250 2.0.0 Ok");
smtp_flush(state->stream);
}
+/* rset_response - reset, send 250 OK */
+
+static void rset_response(SINK_STATE *state, const char *unused_args)
+{
+ mail_cmd_reset(state);
+ smtp_printf(state->stream, "250 2.1.0 Ok");
+ smtp_flush(state->stream);
+}
+
/* mail_response - reset recipient count, send 250 OK */
-static void mail_response(SINK_STATE *state)
+static void mail_response(SINK_STATE *state, const char *args)
{
+ if (state->in_mail) {
+ smtp_printf(state->stream, "503 5.5.1 Error: nested MAIL command");
+ smtp_flush(state->stream);
+ return;
+ }
+ state->in_mail++;
state->rcpts = 0;
smtp_printf(state->stream, "250 2.1.0 Ok");
smtp_flush(state->stream);
+ if (single_template) {
+ mail_file_open(state);
+ SKIP(args, *args != ':');
+ SKIP(args, *args == ':');
+ SKIP(args, ISSPACE(*args));
+ vstream_fprintf(state->dump_file, "X-Mail-Args: %s\n", args);
+ }
}
/* rcpt_response - bump recipient count, send 250 OK */
-static void rcpt_response(SINK_STATE *state)
+static void rcpt_response(SINK_STATE *state, const char *args)
{
+ if (state->in_mail == 0) {
+ smtp_printf(state->stream, "503 5.5.1 Error: need MAIL command");
+ smtp_flush(state->stream);
+ return;
+ }
state->rcpts++;
smtp_printf(state->stream, "250 2.1.5 Ok");
smtp_flush(state->stream);
+ /* Note: there may be more than one recipient per mail transaction. */
+ if (state->dump_file) {
+ SKIP(args, *args != ':');
+ SKIP(args, *args == ':');
+ SKIP(args, ISSPACE(*args));
+ vstream_fprintf(state->dump_file, "X-Rcpt-Args: %s\n", args);
+ }
}
/* data_response - respond to DATA command */
-static void data_response(SINK_STATE *state)
+static void data_response(SINK_STATE *state, const char *unused_args)
{
+ if (state->in_mail == 0 || state->rcpts == 0) {
+ smtp_printf(state->stream, "503 5.5.1 Error: need RCPT command");
+ smtp_flush(state->stream);
+ return;
+ }
+ /* Not: ST_ANY. */
state->data_state = ST_CR_LF;
smtp_printf(state->stream, "354 End data with <CR><LF>.<CR><LF>");
smtp_flush(state->stream);
state->read_fn = data_read;
+ if (state->dump_file)
+ mail_file_finish_header(state);
}
/* data_event - delayed response to DATA command */
{
SINK_STATE *state = (SINK_STATE *) context;
- data_response(state);
+ data_response(state, "");
}
/* dot_resp_hard - hard error response to . command */
/* dot_response - response to . command */
-static void dot_response(SINK_STATE *state)
+static void dot_response(SINK_STATE *state, const char *unused_args)
{
if (enable_lmtp) {
while (state->rcpts-- > 0) /* XXX this could block */
/* quit_response - respond to QUIT command */
-static void quit_response(SINK_STATE *state)
+static void quit_response(SINK_STATE *state, const char *unused_args)
{
smtp_printf(state->stream, "221 Bye");
smtp_flush(state->stream);
/* conn_response - respond to connect command */
-static void conn_response(SINK_STATE *state)
+static void conn_response(SINK_STATE *state, const char *unused_args)
{
if (pretend_pix)
smtp_printf(state->stream, "220 ********");
state->data_state = data_trans[0].next_state;
else
state->data_state = ST_ANY;
+ if (state->dump_file) {
+ if (ch != '\r' && state->data_state != ST_CR_LF_DOT)
+ VSTREAM_PUTC(ch, state->dump_file);
+ if (vstream_ferror(state->dump_file))
+ msg_fatal("append file %s: %m", VSTREAM_PATH(state->dump_file));
+ }
if (state->data_state == ST_CR_LF_DOT_CR_LF) {
PUSH_BACK_SET(state, ".\r\n");
state->read_fn = command_read;
state->data_state = ST_ANY;
+ if (state->dump_file)
+ mail_file_finish(state);
+ mail_cmd_reset(state);
if (count) {
mesg_count++;
do_stats();
*/
typedef struct SINK_COMMAND {
const char *name;
- void (*response) (SINK_STATE *);
+ void (*response) (SINK_STATE *, const char *);
void (*hard_response) (SINK_STATE *);
void (*soft_response) (SINK_STATE *);
int flags;
"rcpt", rcpt_response, hard_err_resp, soft_err_resp, FLAG_ENABLE,
"data", data_response, hard_err_resp, soft_err_resp, FLAG_ENABLE,
".", dot_response, dot_resp_hard, dot_resp_soft, FLAG_ENABLE,
- "rset", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE,
+ "rset", rset_response, hard_err_resp, soft_err_resp, FLAG_ENABLE,
"noop", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE,
"vrfy", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE,
"quit", quit_response, hard_err_resp, soft_err_resp, FLAG_ENABLE,
/* command_resp - respond to command */
-static int command_resp(SINK_STATE *state, SINK_COMMAND *cmdp, const char *command, char *args)
+static int command_resp(SINK_STATE *state, SINK_COMMAND *cmdp,
+ const char *command, const char *args)
{
/* We use raw syslog. Sanitize data content and length. */
if (cmdp->flags & FLAG_SYSLOG)
- syslog(LOG_INFO, "%s %.100s", command, printable(args, '?'));
+ syslog(LOG_INFO, "%s %.100s", command, args);
if (cmdp->flags & FLAG_DISCONNECT)
return (-1);
if (cmdp->flags & FLAG_HARD_ERR) {
if (cmdp->response == data_response && fixed_delay > 0) {
event_request_timer(data_event, (char *) state, fixed_delay);
} else {
- cmdp->response(state);
+ cmdp->response(state, args);
if (cmdp->response == quit_response)
return (-1);
}
*/
vstring_truncate(state->buffer, VSTRING_LEN(state->buffer) - 2);
VSTRING_TERMINATE(state->buffer);
- state->data_state = ST_ANY;
+ state->data_state = ST_CR_LF;
VSTRING_RESET(state->buffer);
/*
smtp_flush(state->stream);
return (0);
}
- return (command_resp(state, cmdp, command, ptr));
+ return (command_resp(state, cmdp, command, printable(ptr, '?')));
}
/* read_timeout - handle timer event */
static void disconnect(SINK_STATE *state)
{
+ static void connect_event(int, char *);
+
event_disable_readwrite(vstream_fileno(state->stream));
event_cancel_timer(read_timeout, (char *) state);
if (count) {
}
vstream_fclose(state->stream);
vstring_free(state->buffer);
+ /* Clean up file capture attributes. */
+ if (state->helo_args)
+ myfree(state->helo_args);
+ /* Delete incomplete mail transaction. */
+ mail_cmd_reset(state);
myfree((char *) state);
if (max_quit_count > 0 && quit_count >= max_quit_count)
exit(0);
+ if (client_count-- == max_client_count)
+ event_enable_read(sock, connect_event, (char *) 0);
}
/* connect_event - handle connection events */
-static void connect_event(int unused_event, char *context)
+static void connect_event(int unused_event, char *unused_context)
{
- int sock = CAST_CHAR_PTR_TO_INT(context);
struct sockaddr sa;
SOCKADDR_SIZE len = sizeof(sa);
SINK_STATE *state;
int fd;
if ((fd = sane_accept(sock, &sa, &len)) >= 0) {
+ /* Safety: limit the number of open sockets and capture files. */
+ if (++client_count == max_client_count)
+ event_disable_readwrite(sock);
+ state = (SINK_STATE *) mymalloc(sizeof(*state));
+ SOCKADDR_TO_HOSTADDR(&sa, len, &state->client_addr,
+ (MAI_SERVPORT_STR *) 0, sa.sa_family);
if (msg_verbose)
- msg_info("connect (%s)",
+ msg_info("connect (%s %s)",
#ifdef AF_LOCAL
sa.sa_family == AF_LOCAL ? "AF_LOCAL" :
#else
#ifdef AF_INET6
sa.sa_family == AF_INET6 ? "AF_INET6" :
#endif
- "unknown protocol family");
+ "unknown protocol family",
+ state->client_addr.buf);
non_blocking(fd, NON_BLOCKING);
- state = (SINK_STATE *) mymalloc(sizeof(*state));
state->stream = vstream_fdopen(fd, O_RDWR);
state->buffer = vstring_alloc(1024);
state->read_fn = command_read;
state->data_state = ST_ANY;
PUSH_BACK_SET(state, "");
smtp_timeout_setup(state->stream, var_tmout);
+ state->in_mail = 0;
+ state->rcpts = 0;
+ /* Initialize file capture attributes. */
+ state->addr_prefix = (sa.sa_family == AF_INET6 ? "ipv6:" : "");
+ state->helo_args = 0;
+ state->client_proto = enable_lmtp ? "LMTP" : "SMTP";
+ state->start_time = 0;
+ state->id = 0;
+ state->dump_file = 0;
/*
* We use the smtp_stream module to produce output. That module
static void usage(char *myname)
{
- msg_fatal("usage: %s [-468acCeEFLpPv] [-f commands] [-h hostname] [-n count] [-q commands] [-r commands] [-s commands] [-w delay] [host]:port backlog", myname);
+ msg_fatal("usage: %s [-468acCeEFLpPv] [-f commands] [-h hostname] [-m max_concurrency] [-n quit_count] [-q commands] [-r commands] [-s commands] [-w delay] [-d dump-template] [-D dump-template] [-S start-string] [host]:port backlog", myname);
}
int main(int argc, char **argv)
{
- int sock;
int backlog;
int ch;
const char *protocols = INET_PROTO_NAME_ALL;
/*
* Parse JCL.
*/
- while ((ch = GETOPT(argc, argv, "468acCeEf:Fh:Ln:pPq:r:s:t:vw:")) > 0) {
+ while ((ch = GETOPT(argc, argv, "468acCeEf:Fh:Ln:m:pPq:r:s:S:t:vw:d:D:")) > 0) {
switch (ch) {
case '4':
protocols = INET_PROTO_NAME_IPV4;
disable_xclient = 1;
reset_cmd_flags("xclient", FLAG_ENABLE);
break;
+ case 'd':
+ single_template = optarg;
+ break;
+ case 'D':
+ shared_template = optarg;
+ break;
case 'e':
disable_esmtp = 1;
break;
case 'L':
enable_lmtp = 1;
break;
+ case 'm':
+ if ((max_client_count = atoi(optarg)) <= 0)
+ msg_fatal("bad concurrency limit: %s", optarg);
+ break;
case 'n':
if ((max_quit_count = atoi(optarg)) <= 0)
- msg_fatal("bad count: %s", optarg);
+ msg_fatal("bad quit count: %s", optarg);
break;
case 'p':
disable_pipelining = 1;
openlog(basename(argv[0]), LOG_PID, LOG_MAIL);
set_cmds_flags(optarg, FLAG_SYSLOG);
break;
+ case 'S':
+ start_string = vstring_alloc(10);
+ unescape(start_string, optarg);
+ break;
case 't':
if ((var_tmout = atoi(optarg)) <= 0)
msg_fatal("bad timeout: %s", optarg);
usage(argv[0]);
if ((backlog = atoi(argv[optind + 1])) <= 0)
usage(argv[0]);
+ if (single_template && shared_template)
+ msg_fatal("use only one of -d or -D, but not both");
/*
* Initialize.
sock = inet_listen(argv[optind], backlog, BLOCKING);
}
+ if (single_template)
+ mysrand((int) time((time_t *) 0));
+ else if (shared_template)
+ single_template = shared_template;
+
/*
* Start the event handler.
*/
- event_enable_read(sock, connect_event, CAST_INT_TO_CHAR_PTR(sock));
+ event_enable_read(sock, connect_event, (char *) 0);
for (;;)
event_loop(-1);
}
/* Connections can be made to UNIX-domain and IPv4 or IPv6 servers.
/* IPv4 and IPv6 are the default.
/*
+/* Note: this is an unsupported test program. No attempt is made
+/* to maintain compatibility between successive versions.
+/*
/* Arguments:
/* .IP \fB-4\fR
/* Connect to the server with IPv4. This option has no effect when
* Send the standard greeting with our hostname
*/
if ((except = vstream_setjmp(session->stream)) != 0)
- msg_fatal("%s while sending HELO", exception_text(except));
+ msg_fatal("%s while sending %s", exception_text(except), protocol);
command(session->stream, "%s %s", protocol, var_myhostname);
SESSION *session = (SESSION *) context;
RESPONSE *resp;
int except;
+ const char *protocol = (talk_lmtp ? "LHLO" : "HELO");
/*
* Get response to HELO command.
*/
if ((except = vstream_setjmp(session->stream)) != 0)
- msg_fatal("%s while sending HELO", exception_text(except));
+ msg_fatal("%s while sending %s", exception_text(except), protocol);
if ((resp = response(session->stream, buffer))->code / 100 != 2)
- msg_fatal("HELO rejected: %d %s", resp->code, resp->str);
+ msg_fatal("%s rejected: %d %s", protocol, resp->code, resp->str);
send_mail(session);
}
/* DIAGNOSTICS
/* peekfd() returns -1 in case of trouble. The global \fIerrno\fR
/* variable reflects the nature of the problem.
+/* BUGS
+/* On some systems, non-blocking read() may fail even after a
+/* positive return from peekfd(). The smtp-sink program works
+/* around this by using the readable() function instead.
/* LICENSE
/* .ad
/* .fi
*/
if (stream->pid != 0)
msg_panic("vstream_fclose: stream has process");
- if ((stream->buf.flags & VSTREAM_FLAG_WRITE_DOUBLE) != 0)
+ if ((stream->buf.flags & VSTREAM_FLAG_WRITE_DOUBLE) != 0 && stream->fd >= 0)
vstream_fflush(stream);
+ /* Do not remove: vstream_fdclose() depends on this error test. */
err = vstream_ferror(stream);
if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
if (stream->read_fd >= 0)
int vstream_fdclose(VSTREAM *stream)
{
+
+ /*
+ * Flush unwritten output, just like vstream_fclose(). Errors are
+ * reported by vstream_fclose().
+ */
+ if ((stream->buf.flags & VSTREAM_FLAG_WRITE_DOUBLE) != 0)
+ (void) vstream_fflush(stream);
+
+ /*
+ * NOTE: Negative file descriptors are not part of the external
+ * interface. They are for internal use only, in order to support
+ * vstream_fdclose() without a lot of code duplication. Applications that
+ * rely on negative VSTREAM file descriptors will break without warning.
+ */
if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) {
- stream->read_fd = stream->write_fd = -1;
+ stream->fd = stream->read_fd = stream->write_fd = -1;
} else {
stream->fd = -1;
}