From c8e31ae510e006127934ce54413a829933ccae9d Mon Sep 17 00:00:00 2001 From: Wietse Venema Date: Sun, 26 Aug 2018 00:00:00 -0500 Subject: [PATCH] postfix-3.4-20180826 --- postfix/HISTORY | 61 +++ postfix/README_FILES/SMTPD_POLICY_README | 4 +- postfix/RELEASE_NOTES | 116 +++++- postfix/WISHLIST | 3 + postfix/html/SMTPD_POLICY_README.html | 2 + postfix/html/postconf.5.html | 22 +- postfix/html/postscreen.8.html | 1 + postfix/html/smtpd.8.html | 423 ++++++++++---------- postfix/man/man5/postconf.5 | 16 +- postfix/man/man8/postscreen.8 | 1 + postfix/man/man8/smtpd.8 | 5 + postfix/mantools/postlink | 1 + postfix/proto/SMTPD_POLICY_README.html | 2 + postfix/proto/postconf.proto | 16 + postfix/src/global/dict_mysql.c | 13 + postfix/src/global/dict_pgsql.c | 28 +- postfix/src/global/ehlo_mask.c | 2 + postfix/src/global/ehlo_mask.h | 1 + postfix/src/global/mail_params.h | 13 + postfix/src/global/mail_version.h | 2 +- postfix/src/global/smtp_stream.c | 99 ++++- postfix/src/global/smtp_stream.h | 3 + postfix/src/postscreen/postscreen.c | 1 + postfix/src/postscreen/postscreen_smtpd.c | 71 +++- postfix/src/smtp/smtp_sasl_glue.c | 60 ++- postfix/src/smtpd/Makefile.in | 2 + postfix/src/smtpd/smtpd.c | 449 ++++++++++++++++++++-- postfix/src/smtpd/smtpd.h | 27 +- postfix/src/smtpd/smtpd_chat.c | 30 +- postfix/src/smtpd/smtpd_chat.h | 10 + postfix/src/smtpd/smtpd_check.c | 9 +- postfix/src/smtpd/smtpd_sasl_glue.c | 9 +- postfix/src/smtpd/smtpd_state.c | 16 +- postfix/src/util/vbuf.c | 3 +- postfix/src/util/vstream.c | 20 +- postfix/src/util/vstream.h | 4 +- postfix/src/util/vstring_vstream.c | 92 +++-- postfix/src/util/vstring_vstream.h | 34 +- 38 files changed, 1330 insertions(+), 341 deletions(-) diff --git a/postfix/HISTORY b/postfix/HISTORY index f15bb2c77..55140ab9f 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -23647,3 +23647,64 @@ Apologies for any names omitted. Bugfix (introduced: 20180812): postscreen_send.c did not build without warnings. Viktor Dukhovni. + +20180824 + + Cleanup: with SMTPUTF8 turned off, the MySQL and PgSQL maps + accept only well-formed UTF-8 queries, and return NOT FOUND + otherwise. This was in introduced in Postfix 3.0 for LDAP + and SQLite, with no complaints coming forth. Files: + global/dict_mysql.c, global/dict_pgsql.c. + +20180805-20180825 Chunking support + + Cleanup: vbuf_get() now sets the EOF flag, so that reading + from a VSTRING stream works as expected. File: util/vbuf.c. + + Cleanup: added an append-mode flag to functions that read + a VSTRING from a stream. The historical APIs are preserved + in the form of aliases. Files: util/vstring_vstream.[hc], + global/smtp_stream.[hc]. + + SMTP server support for CHUNKING (BDAT) per RFC 3030. The + SMTP server is the only program that knows the difference + between mail received with BDAT or DATA. Both use the same + smtpd_data_restrictions and smtpd_end_of_data_restrictions, + both send one Milter DATA event per mail transaction, and + both send one DATA command ending in . + to an smtpd_proxy_filter. Files: global/ehlo_mask.h, + global/smtp_stream.c, global/smtp_stream.c, global/smtp_stream.h, + postscreen/postscreen_smtpd.c, smtpd/smtpd.c, smtpd/smtpd.h, + smtpd/smtpd_chat.c, smtpd/smtpd_chat.h, smtpd/smtpd_state.c. + + Cleanup: the postscreen(8) daemon now hangs up after receiving + the DATA command. Justification: it should never receive DATA + from a legitimate client, because 1) postscreen(8) rejects all + recipients, and 2) postscreen(8) does not announce PIPELINING. + This makes postscreen(8) DATA and BDAT behavior more + consistent. File: postscreen/postscreen_smtpd.c. + + BDAT final touches: report accurate BDAT byte counts after + timeout or lost connection; send DATA instead of BDAT in + policy delegation protocol. Files: smtpd/smtpd.[hc], + smtpd/smtpd_check.c. + + BDAT final touches: if the BDAT EHLO announcement is disabled, + then smtpd(8) and postscreen(8) will not accept BDAT commands. + Files: smtpd/smtpd.c, postscreen/postscreen_smtpd.c. + +20180826 + + Cleanup: with GSSAPI, the Postfix SMTP client's initial + SASL response may be as large as 12288 bytes. When the "AUTH + " command would exceed the SMTP + command length of 512 bytes, send the initial response + during the SASL dialog. Viktor Dukhovni. File: + smtp/smtp_sasl_glue.c. + + Cleanup: prepare the Postfix SMTP server needs to receive + SASL responses that exceed the line_length_limit value. + This introduces a new parameter smtpd_sasl_response_limit + (default: 12288). Viktor Dukhovni. Files: mantools/postlink, + proto/postconf.proto, global/mail_params.h, smtpd/smtpd.c, + smtpd/smtpd_chat.c, smtpd/smtpd_chat.h, smtpd/smtpd_sasl_glue.c. diff --git a/postfix/README_FILES/SMTPD_POLICY_README b/postfix/README_FILES/SMTPD_POLICY_README index aaa30260a..d5ad0b3c4 100644 --- a/postfix/README_FILES/SMTPD_POLICY_README +++ b/postfix/README_FILES/SMTPD_POLICY_README @@ -166,7 +166,9 @@ The following is specific to SMTPD delegated policy requests: * Protocol states are CONNECT, EHLO, HELO, MAIL, RCPT, DATA, END-OF-MESSAGE, VRFY or ETRN; these are the SMTP protocol states where the Postfix SMTP - server makes an OK/REJECT/HOLD/etc. decision. + server makes an OK/REJECT/HOLD/etc. decision. The DATA protocol state also + applies to email that is received with BDAT commands (Postfix 3.4 and + later). The policy server replies with any action that is allowed in a Postfix SMTPD access(5) table. Example: diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index 3c429a651..2a68729b0 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -25,8 +25,120 @@ more recent Eclipse Public License 2.0. Recipients can choose to take the software under the license of their choice. Those who are more comfortable with the IPL can continue with that license. -Incompatble change with snapshot 20180701 -========================================= +Major changes with snapshot 20180826 +==================================== + +Postfix SMTP server support for RFC 3030 CHUNKING (the BDAT command) +without BINARYMIME, in both smtpd(8) and postscreen(8). + +To disable the SMTP server's CHUNKING support: + +/etc/postfix/main.cf: + # The logging alternative: + smtpd_discard_ehlo_keywords = chunking + # The non-logging alternative: + smtpd_discard_ehlo_keywords = chunking, silent_discard + +Be sure to specify '-o smtpd_discard_ehlo_keywords=' in master.cf +for the submission and smtps services, in case you have clients +that benefit from CHUNKING support. + +Impact on existing configurations: +---------------------------------- + +- There are no changes for smtpd_mumble_restrictions, smtpd_proxy_filter, + smtpd_milters, or for postscreen settings, except for the additional + option to suppress the SMTP server's CHUNKING service announcement. + +- There are no changes in the Postfix queue file content, no changes + for down-stream SMTP servers or after-queue content filters, and + no changes in the envelope or message content that Milters will + receive. + +Example SMTP session +-------------------- + +The main differences are that the Postfix SMTP server announces +"CHUNKING" support in the EHLO response, and that instead of sending +one DATA request, the remote SMTP client may send one or more BDAT +requests. In the example below, "S:" indicates server responses, +and "C:" indicates client requests. + + S: 220 server.example.com + C: EHLO client.example.com + S: 250-server.example.com + S: 250-PIPELINING + S: 250-SIZE 153600000 + S: 250-VRFY + S: 250-ETRN + S: 250-STARTTLS + S: 250-AUTH PLAIN LOGIN + S: 250-ENHANCEDSTATUSCODES + S: 250-8BITMIME + S: 250-DSN + S: 250-SMTPUTF8 + S: 250 CHUNKING + C: MAIL FROM: + S: 250 2.1.0 Ok + C: RCPT TO: + S: 250 2.1.5 Ok + C: BDAT 10000 + C: ..followed by 10000 bytes... + S: 250 2.0.0 Ok: 10000 bytes + C: BDAT 123 + C: ..followed by 123 bytes... + S: 250 2.0.0 Ok: 123 bytes + C: BDAT 0 LAST + S: 250 2.0.0 Ok: 10123 bytes queued as 41yYhh41qmznjbD + C: QUIT + S: 221 2.0.0 Bye + +Internally in Postfix, there is no difference between mail that was +received with BDAT or with DATA. Postfix smtpd_mumble_restrictions, +policy delegation queries, smtpd_proxy_filter and Milters all behave +as if Postfix received (MAIL + RCPT + DATA + end-of-data). However, +Postfix will log BDAT-related failures as "xxx after BDAT" to avoid +complicating troubleshooting (xxx = 'lost connection' or 'timeout'), +and will log a warning when a client sends a malformed BDAT command. + +Benefits of CHUNKING (BDAT) support without BINARYMIME: +------------------------------------------------------- + +Support for CHUNKING (BDAT) was added to improve interoperability +with some clients, a benefit that would reportedly exist even without +Postfix support for BINARYMIME. + +Postfix does not support BINARYMIME at this time because: + +- BINARYMIME support would require moderately invasive changes to + support email content that is not line-oriented. With BINARYMIME, + the Content-Length: header specifies the length of arbitrary + content that has no line boundaries. Without BINARYMIME, binary + content is base64-encoded, and formatted as lines of text. + +- There is no conversion of BINARYMIME to a line-oriented 8BITMIME + form that is compatible with legacy systems including UNIX mbox. + The available options are to convert binary content into one of + the 7bit forms (base64 or quoted-printable), or to return email + as undeliverable. Any conversion would break digital signatures, + so it would have to happen before signing. + +Downsides of CHUNKING (BDAT) support: +------------------------------------- + +The RFC 3030 authors did not specify any limitations on how clients +may pipeline commands (i.e. send commands without waiting for a +server response). If a server announces PIPELINING support, like +Postfix does, then a remote SMTP client can pipeline all commands +following EHLO, for example, MAIL/RCPT/BDAT/BDAT/MAIL/RCPT/BDAT, +without ever having to wait for a server response. This means that +with BDAT, the Postfix SMTP server cannot distinguish between a +well-behaved client and a spambot, based on their command pipelining +behavior. If you require "reject_unauth_pipelining" to block spambots, +turn off the CHUNKING support announcement as described above. + +Incompatible change with snapshot 20180701 +========================================== To avoid performance loss under load, the tlsproxy(8) daemon now requires a zero process limit in master.cf (this setting is provided diff --git a/postfix/WISHLIST b/postfix/WISHLIST index e0c54ea4e..e9f125c45 100644 --- a/postfix/WISHLIST +++ b/postfix/WISHLIST @@ -1,5 +1,8 @@ Wish list: + In smtpd(8) and postscreen(8), set the ehlo_discard_mask + to ~0 so that STARTTLS, BDAT, DSN, etc. won't work. + In postscreen, don't fork after 'postfix reload' when psc_check_queue_length (and psc_post_queue_length?) is zero. diff --git a/postfix/html/SMTPD_POLICY_README.html b/postfix/html/SMTPD_POLICY_README.html index 8dd2ce1e0..6fb70015c 100644 --- a/postfix/html/SMTPD_POLICY_README.html +++ b/postfix/html/SMTPD_POLICY_README.html @@ -223,6 +223,8 @@ server_port=54321 DATA, END-OF-MESSAGE, VRFY or ETRN; these are the SMTP protocol states where the Postfix SMTP server makes an OK/REJECT/HOLD/etc. decision. + The DATA protocol state also applies to email that is received + with BDAT commands (Postfix 3.4 and later).

diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index cfd56e5bc..cbc3031ee 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -15484,7 +15484,7 @@ SMTP session.

Note: specify $$name in footer text that is looked up from regexp: or pcre:-based smtpd_reject_footer_maps, otherwise the -Postfix server not use the footer text and will log a warning +Postfix server will not use the footer text and will log a warning instead.

@@ -15876,6 +15876,26 @@ configuration file or rendezvous point.

releases it was called smtpd_sasl_application_name.

+ + +
smtpd_sasl_response_limit +(default: 12288)
+ +

The maximum length of a SASL client's response to a server challenge. +When the client's "initial response" is longer than the normal limit for +SMTP commands, the client must omit its initial response, and wait for an +empty server challenge; it can then send what would have been its "initial +response" as a response to the empty server challenge. RFC4954 requires the +server to accept client responses up to at least 12288 octets of +base64-encoded text. The default value is therefore also the minimum value +accepted for this parameter.

+ +

This feature is available in Postfix 3.4 and later. Prior versions use +"line_length_limit", which may need to be raised to accomodate larger client +responses, as may be needed with GSSAPI authenticaiton of Windows AD users +who are members of many groups.

+ +
smtpd_sasl_security_options diff --git a/postfix/html/postscreen.8.html b/postfix/html/postscreen.8.html index a66c5c57e..f1d8d5e9c 100644 --- a/postfix/html/postscreen.8.html +++ b/postfix/html/postscreen.8.html @@ -64,6 +64,7 @@ POSTSCREEN(8) POSTSCREEN(8) RFC 2034 (SMTP Enhanced Status Codes) RFC 2821 (SMTP protocol) Not: RFC 2920 (SMTP Pipelining) + RFC 3030 (CHUNKING without BINARYMIME) RFC 3207 (STARTTLS command) RFC 3461 (SMTP DSN Extension) RFC 3463 (Enhanced Status Codes) diff --git a/postfix/html/smtpd.8.html b/postfix/html/smtpd.8.html index 9515f278b..a4f48c464 100644 --- a/postfix/html/smtpd.8.html +++ b/postfix/html/smtpd.8.html @@ -50,6 +50,7 @@ SMTPD(8) SMTPD(8) RFC 2554 (AUTH command) RFC 2821 (SMTP protocol) RFC 2920 (SMTP pipelining) + RFC 3030 (CHUNKING without BINARYMIME) RFC 3207 (STARTTLS command) RFC 3461 (SMTP DSN extension) RFC 3463 (Enhanced status codes) @@ -388,17 +389,23 @@ SMTPD(8) SMTPD(8) The service name that is passed to the SASL plug-in that is selected with smtpd_sasl_type and smtpd_sasl_path. + Available in Postfix version 3.4 and later: + + smtpd_sasl_response_limit (12288) + The maximum length of a SASL client's response to a server chal- + lenge. + STARTTLS SUPPORT CONTROLS - Detailed information about STARTTLS configuration may be found in the + Detailed information about STARTTLS configuration may be found in the TLS_README document. smtpd_tls_security_level (empty) - The SMTP TLS security level for the Postfix SMTP server; when a + The SMTP TLS security level for the Postfix SMTP server; when a non-empty value is specified, this overrides the obsolete param- eters smtpd_use_tls and smtpd_enforce_tls. smtpd_sasl_tls_security_options ($smtpd_sasl_security_options) - The SASL authentication security options that the Postfix SMTP + The SASL authentication security options that the Postfix SMTP server uses for TLS encrypted SMTP sessions. smtpd_starttls_timeout (see 'postconf -d' output) @@ -406,25 +413,25 @@ SMTPD(8) SMTPD(8) during TLS startup and shutdown handshake procedures. smtpd_tls_CAfile (empty) - A file containing (PEM format) CA certificates of root CAs + A file containing (PEM format) CA certificates of root CAs trusted to sign either remote SMTP client certificates or inter- mediate CA certificates. smtpd_tls_CApath (empty) - A directory containing (PEM format) CA certificates of root CAs + A directory containing (PEM format) CA certificates of root CAs trusted to sign either remote SMTP client certificates or inter- mediate CA certificates. smtpd_tls_always_issue_session_ids (yes) - Force the Postfix SMTP server to issue a TLS session id, even - when TLS session caching is turned off (smtpd_tls_ses- + Force the Postfix SMTP server to issue a TLS session id, even + when TLS session caching is turned off (smtpd_tls_ses- sion_cache_database is empty). smtpd_tls_ask_ccert (no) Ask a remote SMTP client for a client certificate. smtpd_tls_auth_only (no) - When TLS encryption is optional in the Postfix SMTP server, do + When TLS encryption is optional in the Postfix SMTP server, do not announce or accept SASL authentication over unencrypted con- nections. @@ -435,18 +442,18 @@ SMTPD(8) SMTPD(8) File with the Postfix SMTP server RSA certificate in PEM format. smtpd_tls_exclude_ciphers (empty) - List of ciphers or cipher types to exclude from the SMTP server + List of ciphers or cipher types to exclude from the SMTP server cipher list at all TLS security levels. smtpd_tls_dcert_file (empty) File with the Postfix SMTP server DSA certificate in PEM format. smtpd_tls_dh1024_param_file (empty) - File with DH parameters that the Postfix SMTP server should use + File with DH parameters that the Postfix SMTP server should use with non-export EDH ciphers. smtpd_tls_dh512_param_file (empty) - File with DH parameters that the Postfix SMTP server should use + File with DH parameters that the Postfix SMTP server should use with export-grade EDH ciphers. smtpd_tls_dkey_file ($smtpd_tls_dcert_file) @@ -459,35 +466,35 @@ SMTPD(8) SMTPD(8) Enable additional Postfix SMTP server logging of TLS activity. smtpd_tls_mandatory_ciphers (medium) - The minimum TLS cipher grade that the Postfix SMTP server will + The minimum TLS cipher grade that the Postfix SMTP server will use with mandatory TLS encryption. smtpd_tls_mandatory_exclude_ciphers (empty) - Additional list of ciphers or cipher types to exclude from the - Postfix SMTP server cipher list at mandatory TLS security lev- + Additional list of ciphers or cipher types to exclude from the + Postfix SMTP server cipher list at mandatory TLS security lev- els. smtpd_tls_mandatory_protocols (!SSLv2, !SSLv3) - The SSL/TLS protocols accepted by the Postfix SMTP server with + The SSL/TLS protocols accepted by the Postfix SMTP server with mandatory TLS encryption. smtpd_tls_received_header (no) Request that the Postfix SMTP server produces Received: message - headers that include information about the protocol and cipher - used, as well as the remote SMTP client CommonName and client + headers that include information about the protocol and cipher + used, as well as the remote SMTP client CommonName and client certificate issuer CommonName. smtpd_tls_req_ccert (no) - With mandatory TLS encryption, require a trusted remote SMTP + With mandatory TLS encryption, require a trusted remote SMTP client certificate in order to allow TLS connections to proceed. smtpd_tls_wrappermode (no) - Run the Postfix SMTP server in the non-standard "wrapper" mode, + Run the Postfix SMTP server in the non-standard "wrapper" mode, instead of using the STARTTLS command. tls_daemon_random_bytes (32) - The number of pseudo-random bytes that an smtp(8) or smtpd(8) - process requests from the tlsmgr(8) server in order to seed its + The number of pseudo-random bytes that an smtp(8) or smtpd(8) + process requests from the tlsmgr(8) server in order to seed its internal pseudo random number generator (PRNG). tls_high_cipherlist (see 'postconf -d' output) @@ -503,41 +510,41 @@ SMTPD(8) SMTPD(8) The OpenSSL cipherlist for "export" or higher grade ciphers. tls_null_cipherlist (eNULL:!aNULL) - The OpenSSL cipherlist for "NULL" grade ciphers that provide + The OpenSSL cipherlist for "NULL" grade ciphers that provide authentication without encryption. Available in Postfix version 2.5 and later: smtpd_tls_fingerprint_digest (md5) - The message digest algorithm to construct remote SMTP - client-certificate fingerprints or public key fingerprints - (Postfix 2.9 and later) for check_ccert_access and per- + The message digest algorithm to construct remote SMTP + client-certificate fingerprints or public key fingerprints + (Postfix 2.9 and later) for check_ccert_access and per- mit_tls_clientcerts. Available in Postfix version 2.6 and later: smtpd_tls_protocols (!SSLv2, !SSLv3) - List of TLS protocols that the Postfix SMTP server will exclude + List of TLS protocols that the Postfix SMTP server will exclude or include with opportunistic TLS encryption. smtpd_tls_ciphers (medium) - The minimum TLS cipher grade that the Postfix SMTP server will + The minimum TLS cipher grade that the Postfix SMTP server will use with opportunistic TLS encryption. smtpd_tls_eccert_file (empty) - File with the Postfix SMTP server ECDSA certificate in PEM for- + File with the Postfix SMTP server ECDSA certificate in PEM for- mat. smtpd_tls_eckey_file ($smtpd_tls_eccert_file) - File with the Postfix SMTP server ECDSA private key in PEM for- + File with the Postfix SMTP server ECDSA private key in PEM for- mat. smtpd_tls_eecdh_grade (see 'postconf -d' output) - The Postfix SMTP server security grade for ephemeral ellip- + The Postfix SMTP server security grade for ephemeral ellip- tic-curve Diffie-Hellman (EECDH) key exchange. tls_eecdh_strong_curve (prime256v1) - The elliptic curve used by the Postfix SMTP server for sensibly + The elliptic curve used by the Postfix SMTP server for sensibly strong ephemeral ECDH key exchange. tls_eecdh_ultra_curve (secp384r1) @@ -548,7 +555,7 @@ SMTPD(8) SMTPD(8) tls_preempt_cipherlist (no) With SSLv3 and later, use the Postfix SMTP server's cipher pref- - erence order instead of the remote client's cipher preference + erence order instead of the remote client's cipher preference order. tls_disable_workarounds (see 'postconf -d' output) @@ -561,7 +568,7 @@ SMTPD(8) SMTPD(8) Available in Postfix version 3.0 and later: - tls_session_ticket_cipher (Postfix >= 3.0: aes-256-cbc, Postfix < 3.0: + tls_session_ticket_cipher (Postfix >= 3.0: aes-256-cbc, Postfix < 3.0: aes-128-cbc) Algorithm used to encrypt RFC5077 TLS session tickets. @@ -572,12 +579,12 @@ SMTPD(8) SMTPD(8) SMTP client and server. OBSOLETE STARTTLS CONTROLS - The following configuration parameters exist for compatibility with - Postfix versions before 2.3. Support for these will be removed in a + The following configuration parameters exist for compatibility with + Postfix versions before 2.3. Support for these will be removed in a future release. smtpd_use_tls (no) - Opportunistic TLS: announce STARTTLS support to remote SMTP + Opportunistic TLS: announce STARTTLS support to remote SMTP clients, but do not require that clients use TLS encryption. smtpd_enforce_tls (no) @@ -585,92 +592,92 @@ SMTPD(8) SMTPD(8) and require that clients use TLS encryption. smtpd_tls_cipherlist (empty) - Obsolete Postfix < 2.3 control for the Postfix SMTP server TLS + Obsolete Postfix < 2.3 control for the Postfix SMTP server TLS cipher list. SMTPUTF8 CONTROLS Preliminary SMTPUTF8 support is introduced with Postfix 3.0. smtputf8_enable (yes) - Enable preliminary SMTPUTF8 support for the protocols described + Enable preliminary SMTPUTF8 support for the protocols described in RFC 6531..6533. strict_smtputf8 (no) Enable stricter enforcement of the SMTPUTF8 protocol. smtputf8_autodetect_classes (sendmail, verify) - Detect that a message requires SMTPUTF8 support for the speci- + Detect that a message requires SMTPUTF8 support for the speci- fied mail origin classes. Available in Postfix version 3.2 and later: enable_idna2003_compatibility (no) - Enable 'transitional' compatibility between IDNA2003 and - IDNA2008, when converting UTF-8 domain names to/from the ASCII + Enable 'transitional' compatibility between IDNA2003 and + IDNA2008, when converting UTF-8 domain names to/from the ASCII form that is used for DNS lookups. VERP SUPPORT CONTROLS - With VERP style delivery, each recipient of a message receives a cus- - tomized copy of the message with his/her own recipient address encoded + With VERP style delivery, each recipient of a message receives a cus- + tomized copy of the message with his/her own recipient address encoded in the envelope sender address. The VERP_README file describes config- - uration and operation details of Postfix support for variable envelope - return path addresses. VERP style delivery is requested with the SMTP - XVERP command or with the "sendmail -V" command-line option and is + uration and operation details of Postfix support for variable envelope + return path addresses. VERP style delivery is requested with the SMTP + XVERP command or with the "sendmail -V" command-line option and is available in Postfix version 1.1 and later. default_verp_delimiters (+=) The two default VERP delimiter characters. verp_delimiter_filter (-=+) - The characters Postfix accepts as VERP delimiter characters on + The characters Postfix accepts as VERP delimiter characters on the Postfix sendmail(1) command line and in SMTP commands. Available in Postfix version 1.1 and 2.0: authorized_verp_clients ($mynetworks) - What remote SMTP clients are allowed to specify the XVERP com- + What remote SMTP clients are allowed to specify the XVERP com- mand. Available in Postfix version 2.1 and later: smtpd_authorized_verp_clients ($authorized_verp_clients) - What remote SMTP clients are allowed to specify the XVERP com- + What remote SMTP clients are allowed to specify the XVERP com- mand. TROUBLE SHOOTING CONTROLS - The DEBUG_README document describes how to debug parts of the Postfix - mail system. The methods vary from making the software log a lot of + The DEBUG_README document describes how to debug parts of the Postfix + mail system. The methods vary from making the software log a lot of detail, to running some daemon processes under control of a call tracer or debugger. debug_peer_level (2) - The increment in verbose logging level when a remote client or + The increment in verbose logging level when a remote client or server matches a pattern in the debug_peer_list parameter. debug_peer_list (empty) - Optional list of remote client or server hostname or network + Optional list of remote client or server hostname or network address patterns that cause the verbose logging level to increase by the amount specified in $debug_peer_level. error_notice_recipient (postmaster) - The recipient of postmaster notifications about mail delivery + The recipient of postmaster notifications about mail delivery problems that are caused by policy, resource, software or proto- col errors. internal_mail_filter_classes (empty) - What categories of Postfix-generated mail are subject to - before-queue content inspection by non_smtpd_milters, + What categories of Postfix-generated mail are subject to + before-queue content inspection by non_smtpd_milters, header_checks and body_checks. notify_classes (resource, software) The list of error classes that are reported to the postmaster. smtpd_reject_footer (empty) - Optional information that is appended after each Postfix SMTP + Optional information that is appended after each Postfix SMTP server 4XX or 5XX response. soft_bounce (no) - Safety net to keep mail queued that would otherwise be returned + Safety net to keep mail queued that would otherwise be returned to the sender. Available in Postfix version 2.1 and later: @@ -681,109 +688,109 @@ SMTPD(8) SMTPD(8) Available in Postfix version 2.10 and later: smtpd_log_access_permit_actions (empty) - Enable logging of the named "permit" actions in SMTP server - access lists (by default, the SMTP server logs "reject" actions + Enable logging of the named "permit" actions in SMTP server + access lists (by default, the SMTP server logs "reject" actions but not "permit" actions). KNOWN VERSUS UNKNOWN RECIPIENT CONTROLS - As of Postfix version 2.0, the SMTP server rejects mail for unknown + As of Postfix version 2.0, the SMTP server rejects mail for unknown recipients. This prevents the mail queue from clogging up with undeliv- - erable MAILER-DAEMON messages. Additional information on this topic is + erable MAILER-DAEMON messages. Additional information on this topic is in the LOCAL_RECIPIENT_README and ADDRESS_CLASS_README documents. show_user_unknown_table_name (yes) - Display the name of the recipient table in the "User unknown" + Display the name of the recipient table in the "User unknown" responses. canonical_maps (empty) - Optional address mapping lookup tables for message headers and + Optional address mapping lookup tables for message headers and envelopes. recipient_canonical_maps (empty) - Optional address mapping lookup tables for envelope and header + Optional address mapping lookup tables for envelope and header recipient addresses. sender_canonical_maps (empty) - Optional address mapping lookup tables for envelope and header + Optional address mapping lookup tables for envelope and header sender addresses. Parameters concerning known/unknown local recipients: mydestination ($myhostname, localhost.$mydomain, localhost) - The list of domains that are delivered via the $local_transport + The list of domains that are delivered via the $local_transport mail delivery transport. inet_interfaces (all) - The network interface addresses that this mail system receives + The network interface addresses that this mail system receives mail on. proxy_interfaces (empty) - The network interface addresses that this mail system receives + The network interface addresses that this mail system receives mail on by way of a proxy or network address translation unit. inet_protocols (all) - The Internet protocols Postfix will attempt to use when making + The Internet protocols Postfix will attempt to use when making or accepting connections. local_recipient_maps (proxy:unix:passwd.byname $alias_maps) Lookup tables with all names or addresses of local recipients: a - recipient address is local when its domain matches $mydestina- + recipient address is local when its domain matches $mydestina- tion, $inet_interfaces or $proxy_interfaces. unknown_local_recipient_reject_code (550) The numerical Postfix SMTP server response code when a recipient - address is local, and $local_recipient_maps specifies a list of + address is local, and $local_recipient_maps specifies a list of lookup tables that does not match the recipient. Parameters concerning known/unknown recipients of relay destinations: relay_domains (Postfix >= 3.0: empty, Postfix < 3.0: $mydestination) - What destination domains (and subdomains thereof) this system + What destination domains (and subdomains thereof) this system will relay mail to. relay_recipient_maps (empty) - Optional lookup tables with all valid addresses in the domains + Optional lookup tables with all valid addresses in the domains that match $relay_domains. unknown_relay_recipient_reject_code (550) - The numerical Postfix SMTP server reply code when a recipient - address matches $relay_domains, and relay_recipient_maps speci- - fies a list of lookup tables that does not match the recipient + The numerical Postfix SMTP server reply code when a recipient + address matches $relay_domains, and relay_recipient_maps speci- + fies a list of lookup tables that does not match the recipient address. - Parameters concerning known/unknown recipients in virtual alias + Parameters concerning known/unknown recipients in virtual alias domains: virtual_alias_domains ($virtual_alias_maps) - Postfix is final destination for the specified list of virtual - alias domains, that is, domains for which all addresses are + Postfix is final destination for the specified list of virtual + alias domains, that is, domains for which all addresses are aliased to addresses in other local or remote domains. virtual_alias_maps ($virtual_maps) - Optional lookup tables that alias specific mail addresses or + Optional lookup tables that alias specific mail addresses or domains to other local or remote address. unknown_virtual_alias_reject_code (550) - The Postfix SMTP server reply code when a recipient address - matches $virtual_alias_domains, and $virtual_alias_maps speci- - fies a list of lookup tables that does not match the recipient + The Postfix SMTP server reply code when a recipient address + matches $virtual_alias_domains, and $virtual_alias_maps speci- + fies a list of lookup tables that does not match the recipient address. Parameters concerning known/unknown recipients in virtual mailbox domains: virtual_mailbox_domains ($virtual_mailbox_maps) - Postfix is final destination for the specified list of domains; - mail is delivered via the $virtual_transport mail delivery + Postfix is final destination for the specified list of domains; + mail is delivered via the $virtual_transport mail delivery transport. virtual_mailbox_maps (empty) - Optional lookup tables with all valid addresses in the domains + Optional lookup tables with all valid addresses in the domains that match $virtual_mailbox_domains. unknown_virtual_mailbox_reject_code (550) - The Postfix SMTP server reply code when a recipient address - matches $virtual_mailbox_domains, and $virtual_mailbox_maps + The Postfix SMTP server reply code when a recipient address + matches $virtual_mailbox_domains, and $virtual_mailbox_maps specifies a list of lookup tables that does not match the recip- ient address. @@ -792,7 +799,7 @@ SMTPD(8) SMTPD(8) control client request rates. line_length_limit (2048) - Upon input, long lines are chopped up into pieces of at most + Upon input, long lines are chopped up into pieces of at most this length; upon delivery, long lines are reconstructed. queue_minfree (0) @@ -800,58 +807,58 @@ SMTPD(8) SMTPD(8) tem that is needed to receive mail. message_size_limit (10240000) - The maximal size in bytes of a message, including envelope + The maximal size in bytes of a message, including envelope information. smtpd_recipient_limit (1000) - The maximal number of recipients that the Postfix SMTP server + The maximal number of recipients that the Postfix SMTP server accepts per message delivery request. smtpd_timeout (normal: 300s, overload: 10s) - The time limit for sending a Postfix SMTP server response and + The time limit for sending a Postfix SMTP server response and for receiving a remote SMTP client request. smtpd_history_flush_threshold (100) - The maximal number of lines in the Postfix SMTP server command - history before it is flushed upon receipt of EHLO, RSET, or end + The maximal number of lines in the Postfix SMTP server command + history before it is flushed upon receipt of EHLO, RSET, or end of DATA. Available in Postfix version 2.3 and later: smtpd_peername_lookup (yes) - Attempt to look up the remote SMTP client hostname, and verify + Attempt to look up the remote SMTP client hostname, and verify that the name matches the client IP address. The per SMTP client connection count and request rate limits are imple- - mented in co-operation with the anvil(8) service, and are available in + mented in co-operation with the anvil(8) service, and are available in Postfix version 2.2 and later. smtpd_client_connection_count_limit (50) - How many simultaneous connections any client is allowed to make + How many simultaneous connections any client is allowed to make to this service. smtpd_client_connection_rate_limit (0) - The maximal number of connection attempts any client is allowed + The maximal number of connection attempts any client is allowed to make to this service per time unit. smtpd_client_message_rate_limit (0) - The maximal number of message delivery requests that any client - is allowed to make to this service per time unit, regardless of + The maximal number of message delivery requests that any client + is allowed to make to this service per time unit, regardless of whether or not Postfix actually accepts those messages. smtpd_client_recipient_rate_limit (0) - The maximal number of recipient addresses that any client is - allowed to send to this service per time unit, regardless of + The maximal number of recipient addresses that any client is + allowed to send to this service per time unit, regardless of whether or not Postfix actually accepts those recipients. smtpd_client_event_limit_exceptions ($mynetworks) - Clients that are excluded from smtpd_client_*_count/rate_limit + Clients that are excluded from smtpd_client_*_count/rate_limit restrictions. Available in Postfix version 2.3 and later: smtpd_client_new_tls_session_rate_limit (0) - The maximal number of new (i.e., uncached) TLS sessions that a + The maximal number of new (i.e., uncached) TLS sessions that a remote SMTP client is allowed to negotiate with this service per time unit. @@ -859,68 +866,68 @@ SMTPD(8) SMTPD(8) smtpd_per_record_deadline (normal: no, overload: yes) Change the behavior of the smtpd_timeout and smtpd_start- - tls_timeout time limits, from a time limit per read or write - system call, to a time limit to send or receive a complete - record (an SMTP command line, SMTP response line, SMTP message + tls_timeout time limits, from a time limit per read or write + system call, to a time limit to send or receive a complete + record (an SMTP command line, SMTP response line, SMTP message content line, or TLS protocol message). Available in Postfix version 3.1 and later: smtpd_client_auth_rate_limit (0) - The maximal number of AUTH commands that any client is allowed - to send to this service per time unit, regardless of whether or + The maximal number of AUTH commands that any client is allowed + to send to this service per time unit, regardless of whether or not Postfix actually accepts those commands. TARPIT CONTROLS - When a remote SMTP client makes errors, the Postfix SMTP server can - insert delays before responding. This can help to slow down run-away - software. The behavior is controlled by an error counter that counts + When a remote SMTP client makes errors, the Postfix SMTP server can + insert delays before responding. This can help to slow down run-away + software. The behavior is controlled by an error counter that counts the number of errors within an SMTP session that a client makes without delivering mail. smtpd_error_sleep_time (1s) - With Postfix version 2.1 and later: the SMTP server response - delay after a client has made more than $smtpd_soft_error_limit - errors, and fewer than $smtpd_hard_error_limit errors, without + With Postfix version 2.1 and later: the SMTP server response + delay after a client has made more than $smtpd_soft_error_limit + errors, and fewer than $smtpd_hard_error_limit errors, without delivering mail. smtpd_soft_error_limit (10) - The number of errors a remote SMTP client is allowed to make - without delivering mail before the Postfix SMTP server slows + The number of errors a remote SMTP client is allowed to make + without delivering mail before the Postfix SMTP server slows down all its responses. smtpd_hard_error_limit (normal: 20, overload: 1) - The maximal number of errors a remote SMTP client is allowed to + The maximal number of errors a remote SMTP client is allowed to make without delivering mail. smtpd_junk_command_limit (normal: 100, overload: 1) - The number of junk commands (NOOP, VRFY, ETRN or RSET) that a - remote SMTP client can send before the Postfix SMTP server + The number of junk commands (NOOP, VRFY, ETRN or RSET) that a + remote SMTP client can send before the Postfix SMTP server starts to increment the error counter with each junk command. Available in Postfix version 2.1 and later: smtpd_recipient_overshoot_limit (1000) - The number of recipients that a remote SMTP client can send in + The number of recipients that a remote SMTP client can send in excess of the limit specified with $smtpd_recipient_limit, - before the Postfix SMTP server increments the per-session error + before the Postfix SMTP server increments the per-session error count for each excess recipient. ACCESS POLICY DELEGATION CONTROLS - As of version 2.1, Postfix can be configured to delegate access policy - decisions to an external server that runs outside Postfix. See the + As of version 2.1, Postfix can be configured to delegate access policy + decisions to an external server that runs outside Postfix. See the file SMTPD_POLICY_README for more information. smtpd_policy_service_max_idle (300s) - The time after which an idle SMTPD policy service connection is + The time after which an idle SMTPD policy service connection is closed. smtpd_policy_service_max_ttl (1000s) - The time after which an active SMTPD policy service connection + The time after which an active SMTPD policy service connection is closed. smtpd_policy_service_timeout (100s) - The time limit for connecting to, writing to, or receiving from + The time limit for connecting to, writing to, or receiving from a delegated SMTPD policy server. Available in Postfix version 3.0 and later: @@ -930,81 +937,81 @@ SMTPD(8) SMTPD(8) The default action when an SMTPD policy service request fails. smtpd_policy_service_request_limit (0) - The maximal number of requests per SMTPD policy service connec- + The maximal number of requests per SMTPD policy service connec- tion, or zero (no limit). smtpd_policy_service_try_limit (2) - The maximal number of attempts to send an SMTPD policy service + The maximal number of attempts to send an SMTPD policy service request before giving up. smtpd_policy_service_retry_delay (1s) - The delay between attempts to resend a failed SMTPD policy ser- + The delay between attempts to resend a failed SMTPD policy ser- vice request. Available in Postfix version 3.1 and later: smtpd_policy_service_policy_context (empty) - Optional information that the Postfix SMTP server specifies in - the "policy_context" attribute of a policy service request - (originally, to share the same service endpoint among multiple + Optional information that the Postfix SMTP server specifies in + the "policy_context" attribute of a policy service request + (originally, to share the same service endpoint among multiple check_policy_service clients). ACCESS CONTROLS - The SMTPD_ACCESS_README document gives an introduction to all the SMTP + The SMTPD_ACCESS_README document gives an introduction to all the SMTP server access control features. smtpd_delay_reject (yes) - Wait until the RCPT TO command before evaluating + Wait until the RCPT TO command before evaluating $smtpd_client_restrictions, $smtpd_helo_restrictions and $smtpd_sender_restrictions, or wait until the ETRN command - before evaluating $smtpd_client_restrictions and + before evaluating $smtpd_client_restrictions and $smtpd_helo_restrictions. parent_domain_matches_subdomains (see 'postconf -d' output) - A list of Postfix features where the pattern "example.com" also - matches subdomains of example.com, instead of requiring an + A list of Postfix features where the pattern "example.com" also + matches subdomains of example.com, instead of requiring an explicit ".example.com" pattern. smtpd_client_restrictions (empty) - Optional restrictions that the Postfix SMTP server applies in + Optional restrictions that the Postfix SMTP server applies in the context of a client connection request. smtpd_helo_required (no) - Require that a remote SMTP client introduces itself with the - HELO or EHLO command before sending the MAIL command or other + Require that a remote SMTP client introduces itself with the + HELO or EHLO command before sending the MAIL command or other commands that require EHLO negotiation. smtpd_helo_restrictions (empty) - Optional restrictions that the Postfix SMTP server applies in + Optional restrictions that the Postfix SMTP server applies in the context of a client HELO command. smtpd_sender_restrictions (empty) - Optional restrictions that the Postfix SMTP server applies in + Optional restrictions that the Postfix SMTP server applies in the context of a client MAIL FROM command. smtpd_recipient_restrictions (see 'postconf -d' output) - Optional restrictions that the Postfix SMTP server applies in - the context of a client RCPT TO command, after + Optional restrictions that the Postfix SMTP server applies in + the context of a client RCPT TO command, after smtpd_relay_restrictions. smtpd_etrn_restrictions (empty) - Optional restrictions that the Postfix SMTP server applies in + Optional restrictions that the Postfix SMTP server applies in the context of a client ETRN command. allow_untrusted_routing (no) - Forward mail with sender-specified routing - (user[@%!]remote[@%!]site) from untrusted clients to destina- + Forward mail with sender-specified routing + (user[@%!]remote[@%!]site) from untrusted clients to destina- tions matching $relay_domains. smtpd_restriction_classes (empty) User-defined aliases for groups of access restrictions. smtpd_null_access_lookup_key (<>) - The lookup key to be used in SMTP access(5) tables instead of + The lookup key to be used in SMTP access(5) tables instead of the null sender address. permit_mx_backup_networks (empty) - Restrict the use of the permit_mx_backup SMTP access feature to + Restrict the use of the permit_mx_backup SMTP access feature to only domains whose primary MX hosts match the listed networks. Available in Postfix version 2.0 and later: @@ -1014,19 +1021,19 @@ SMTPD(8) SMTPD(8) applies in the context of the SMTP DATA command. smtpd_expansion_filter (see 'postconf -d' output) - What characters are allowed in $name expansions of RBL reply + What characters are allowed in $name expansions of RBL reply templates. Available in Postfix version 2.1 and later: smtpd_reject_unlisted_sender (no) - Request that the Postfix SMTP server rejects mail from unknown - sender addresses, even when no explicit reject_unlisted_sender + Request that the Postfix SMTP server rejects mail from unknown + sender addresses, even when no explicit reject_unlisted_sender access restriction is specified. smtpd_reject_unlisted_recipient (yes) - Request that the Postfix SMTP server rejects mail for unknown - recipient addresses, even when no explicit + Request that the Postfix SMTP server rejects mail for unknown + recipient addresses, even when no explicit reject_unlisted_recipient access restriction is specified. Available in Postfix version 2.2 and later: @@ -1040,17 +1047,17 @@ SMTPD(8) SMTPD(8) smtpd_relay_restrictions (permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination) Access restrictions for mail relay control that the Postfix SMTP - server applies in the context of the RCPT TO command, before + server applies in the context of the RCPT TO command, before smtpd_recipient_restrictions. SENDER AND RECIPIENT ADDRESS VERIFICATION CONTROLS - Postfix version 2.1 introduces sender and recipient address verifica- + Postfix version 2.1 introduces sender and recipient address verifica- tion. This feature is implemented by sending probe email messages that are not actually delivered. This feature is requested via the - reject_unverified_sender and reject_unverified_recipient access - restrictions. The status of verification probes is maintained by the - verify(8) server. See the file ADDRESS_VERIFICATION_README for infor- - mation about how to configure and operate the Postfix sender/recipient + reject_unverified_sender and reject_unverified_recipient access + restrictions. The status of verification probes is maintained by the + verify(8) server. See the file ADDRESS_VERIFICATION_README for infor- + mation about how to configure and operate the Postfix sender/recipient address verification service. address_verify_poll_count (normal: 3, overload: 1) @@ -1062,7 +1069,7 @@ SMTPD(8) SMTPD(8) fication request in progress. address_verify_sender ($double_bounce_sender) - The sender address to use in address verification probes; prior + The sender address to use in address verification probes; prior to Postfix 2.5 the default was "postmaster". unverified_sender_reject_code (450) @@ -1070,18 +1077,18 @@ SMTPD(8) SMTPD(8) address is rejected by the reject_unverified_sender restriction. unverified_recipient_reject_code (450) - The numerical Postfix SMTP server response when a recipient - address is rejected by the reject_unverified_recipient restric- + The numerical Postfix SMTP server response when a recipient + address is rejected by the reject_unverified_recipient restric- tion. Available in Postfix version 2.6 and later: unverified_sender_defer_code (450) - The numerical Postfix SMTP server response code when a sender + The numerical Postfix SMTP server response code when a sender address probe fails due to a temporary error condition. unverified_recipient_defer_code (450) - The numerical Postfix SMTP server response when a recipient + The numerical Postfix SMTP server response when a recipient address probe fails due to a temporary error condition. unverified_sender_reject_reason (empty) @@ -1093,17 +1100,17 @@ SMTPD(8) SMTPD(8) reject_unverified_recipient. unverified_sender_tempfail_action ($reject_tempfail_action) - The Postfix SMTP server's action when reject_unverified_sender + The Postfix SMTP server's action when reject_unverified_sender fails due to a temporary error condition. unverified_recipient_tempfail_action ($reject_tempfail_action) - The Postfix SMTP server's action when reject_unverified_recipi- + The Postfix SMTP server's action when reject_unverified_recipi- ent fails due to a temporary error condition. Available with Postfix 2.9 and later: address_verify_sender_ttl (0s) - The time between changes in the time-dependent portion of + The time between changes in the time-dependent portion of address verification probe sender addresses. ACCESS CONTROL RESPONSES @@ -1115,36 +1122,36 @@ SMTPD(8) SMTPD(8) map "reject" action. defer_code (450) - The numerical Postfix SMTP server response code when a remote + The numerical Postfix SMTP server response code when a remote SMTP client request is rejected by the "defer" restriction. invalid_hostname_reject_code (501) - The numerical Postfix SMTP server response code when the client - HELO or EHLO command parameter is rejected by the + The numerical Postfix SMTP server response code when the client + HELO or EHLO command parameter is rejected by the reject_invalid_helo_hostname restriction. maps_rbl_reject_code (554) - The numerical Postfix SMTP server response code when a remote - SMTP client request is blocked by the reject_rbl_client, + The numerical Postfix SMTP server response code when a remote + SMTP client request is blocked by the reject_rbl_client, reject_rhsbl_client, reject_rhsbl_reverse_client, reject_rhsbl_sender or reject_rhsbl_recipient restriction. non_fqdn_reject_code (504) - The numerical Postfix SMTP server reply code when a client - request is rejected by the reject_non_fqdn_helo_hostname, + The numerical Postfix SMTP server reply code when a client + request is rejected by the reject_non_fqdn_helo_hostname, reject_non_fqdn_sender or reject_non_fqdn_recipient restriction. plaintext_reject_code (450) - The numerical Postfix SMTP server response code when a request + The numerical Postfix SMTP server response code when a request is rejected by the reject_plaintext_session restriction. reject_code (554) - The numerical Postfix SMTP server response code when a remote + The numerical Postfix SMTP server response code when a remote SMTP client request is rejected by the "reject" restriction. relay_domains_reject_code (554) - The numerical Postfix SMTP server response code when a client - request is rejected by the reject_unauth_destination recipient + The numerical Postfix SMTP server response code when a client + request is rejected by the reject_unauth_destination recipient restriction. unknown_address_reject_code (450) @@ -1152,24 +1159,24 @@ SMTPD(8) SMTPD(8) a sender or recipient address because its domain is unknown. unknown_client_reject_code (450) - The numerical Postfix SMTP server response code when a client - without valid address <=> name mapping is rejected by the + The numerical Postfix SMTP server response code when a client + without valid address <=> name mapping is rejected by the reject_unknown_client_hostname restriction. unknown_hostname_reject_code (450) - The numerical Postfix SMTP server response code when the host- - name specified with the HELO or EHLO command is rejected by the + The numerical Postfix SMTP server response code when the host- + name specified with the HELO or EHLO command is rejected by the reject_unknown_helo_hostname restriction. Available in Postfix version 2.0 and later: default_rbl_reply (see 'postconf -d' output) - The default Postfix SMTP server response template for a request + The default Postfix SMTP server response template for a request that is rejected by an RBL-based restriction. multi_recipient_bounce_reject_code (550) - The numerical Postfix SMTP server response code when a remote - SMTP client request is blocked by the reject_multi_recipi- + The numerical Postfix SMTP server response code when a remote + SMTP client request is blocked by the reject_multi_recipi- ent_bounce restriction. rbl_reply_maps (empty) @@ -1179,52 +1186,52 @@ SMTPD(8) SMTPD(8) access_map_defer_code (450) The numerical Postfix SMTP server response code for an access(5) - map "defer" action, including "defer_if_permit" or + map "defer" action, including "defer_if_permit" or "defer_if_reject". reject_tempfail_action (defer_if_permit) - The Postfix SMTP server's action when a reject-type restriction + The Postfix SMTP server's action when a reject-type restriction fails due to a temporary error condition. unknown_helo_hostname_tempfail_action ($reject_tempfail_action) - The Postfix SMTP server's action when reject_unknown_helo_host- + The Postfix SMTP server's action when reject_unknown_helo_host- name fails due to an temporary error condition. unknown_address_tempfail_action ($reject_tempfail_action) - The Postfix SMTP server's action when - reject_unknown_sender_domain or reject_unknown_recipient_domain + The Postfix SMTP server's action when + reject_unknown_sender_domain or reject_unknown_recipient_domain fail due to a temporary error condition. MISCELLANEOUS CONTROLS config_directory (see 'postconf -d' output) - The default location of the Postfix main.cf and master.cf con- + The default location of the Postfix main.cf and master.cf con- figuration files. daemon_timeout (18000s) - How much time a Postfix daemon process may take to handle a + How much time a Postfix daemon process may take to handle a request before it is terminated by a built-in watchdog timer. command_directory (see 'postconf -d' output) The location of all postfix administrative commands. double_bounce_sender (double-bounce) - The sender address of postmaster notifications that are gener- + The sender address of postmaster notifications that are gener- ated by the mail system. ipc_timeout (3600s) - The time limit for sending or receiving information over an + The time limit for sending or receiving information over an internal communication channel. mail_name (Postfix) - The mail system name that is displayed in Received: headers, in + The mail system name that is displayed in Received: headers, in the SMTP greeting banner, and in bounced mail. mail_owner (postfix) - The UNIX system account that owns the Postfix queue and most + The UNIX system account that owns the Postfix queue and most Postfix daemon processes. max_idle (100s) - The maximum amount of time that an idle Postfix daemon process + The maximum amount of time that an idle Postfix daemon process waits for an incoming connection before terminating voluntarily. max_use (100) @@ -1235,11 +1242,11 @@ SMTPD(8) SMTPD(8) The internet hostname of this mail system. mynetworks (see 'postconf -d' output) - The list of "trusted" remote SMTP clients that have more privi- + The list of "trusted" remote SMTP clients that have more privi- leges than "strangers". myorigin ($myhostname) - The domain name that locally-posted mail appears to come from, + The domain name that locally-posted mail appears to come from, and that locally posted mail is delivered to. process_id (read-only) @@ -1252,25 +1259,25 @@ SMTPD(8) SMTPD(8) The location of the Postfix top-level queue directory. recipient_delimiter (empty) - The set of characters that can separate a user name from its - extension (example: user+foo), or a .forward file name from its + The set of characters that can separate a user name from its + extension (example: user+foo), or a .forward file name from its extension (example: .forward+foo). smtpd_banner ($myhostname ESMTP $mail_name) - The text that follows the 220 status code in the SMTP greeting + The text that follows the 220 status code in the SMTP greeting banner. syslog_facility (mail) The syslog facility of Postfix logging. syslog_name (see 'postconf -d' output) - A prefix that is prepended to the process name in syslog + A prefix that is prepended to the process name in syslog records, so that, for example, "smtpd" becomes "prefix/smtpd". Available in Postfix version 2.2 and later: smtpd_forbidden_commands (CONNECT, GET, POST) - List of commands that cause the Postfix SMTP server to immedi- + List of commands that cause the Postfix SMTP server to immedi- ately terminate the session with a 221 code. Available in Postfix version 2.5 and later: @@ -1287,7 +1294,7 @@ SMTPD(8) SMTPD(8) Available in Postfix 3.4 and later: smtpd_reject_footer_maps (empty) - Lookup tables, indexed by the complete Postfix SMTP server 4xx + Lookup tables, indexed by the complete Postfix SMTP server 4xx or 5xx response, with reject footer templates. SEE ALSO diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5 index 1235a155d..2a4e29d1d 100644 --- a/postfix/man/man5/postconf.5 +++ b/postfix/man/man5/postconf.5 @@ -10451,7 +10451,7 @@ SMTP session. .PP Note: specify $$name in footer text that is looked up from regexp: or pcre:\-based smtpd_reject_footer_maps, otherwise the -Postfix server not use the footer text and will log a warning +Postfix server will not use the footer text and will log a warning instead. .IP "\fBclient_address\fR" The Client IP address that @@ -10746,6 +10746,20 @@ configuration file or rendezvous point. .PP This feature is available in Postfix 2.3 and later. In earlier releases it was called \fBsmtpd_sasl_application_name\fR. +.SH smtpd_sasl_response_limit (default: 12288) +The maximum length of a SASL client's response to a server challenge. +When the client's "initial response" is longer than the normal limit for +SMTP commands, the client must omit its initial response, and wait for an +empty server challenge; it can then send what would have been its "initial +response" as a response to the empty server challenge. RFC4954 requires the +server to accept client responses up to at least 12288 octets of +base64\-encoded text. The default value is therefore also the minimum value +accepted for this parameter. +.PP +This feature is available in Postfix 3.4 and later. Prior versions use +"line_length_limit", which may need to be raised to accomodate larger client +responses, as may be needed with GSSAPI authenticaiton of Windows AD users +who are members of many groups. .SH smtpd_sasl_security_options (default: noanonymous) Postfix SMTP server SASL security options; as of Postfix 2.3 the list of available diff --git a/postfix/man/man8/postscreen.8 b/postfix/man/man8/postscreen.8 index df17872c3..7e7a25532 100644 --- a/postfix/man/man8/postscreen.8 +++ b/postfix/man/man8/postscreen.8 @@ -71,6 +71,7 @@ RFC 1985 (ETRN command) RFC 2034 (SMTP Enhanced Status Codes) RFC 2821 (SMTP protocol) Not: RFC 2920 (SMTP Pipelining) +RFC 3030 (CHUNKING without BINARYMIME) RFC 3207 (STARTTLS command) RFC 3461 (SMTP DSN Extension) RFC 3463 (Enhanced Status Codes) diff --git a/postfix/man/man8/smtpd.8 b/postfix/man/man8/smtpd.8 index c51f8ab0b..741ab4dc3 100644 --- a/postfix/man/man8/smtpd.8 +++ b/postfix/man/man8/smtpd.8 @@ -56,6 +56,7 @@ RFC 2034 (SMTP enhanced status codes) RFC 2554 (AUTH command) RFC 2821 (SMTP protocol) RFC 2920 (SMTP pipelining) +RFC 3030 (CHUNKING without BINARYMIME) RFC 3207 (STARTTLS command) RFC 3461 (SMTP DSN extension) RFC 3463 (Enhanced status codes) @@ -370,6 +371,10 @@ Available in Postfix version 2.11 and later: .IP "\fBsmtpd_sasl_service (smtp)\fR" The service name that is passed to the SASL plug\-in that is selected with \fBsmtpd_sasl_type\fR and \fBsmtpd_sasl_path\fR. +.PP +Available in Postfix version 3.4 and later: +.IP "\fBsmtpd_sasl_response_limit (12288)\fR" +The maximum length of a SASL client's response to a server challenge. .SH "STARTTLS SUPPORT CONTROLS" .na .nf diff --git a/postfix/mantools/postlink b/postfix/mantools/postlink index 99257909c..7e6fd4197 100755 --- a/postfix/mantools/postlink +++ b/postfix/mantools/postlink @@ -582,6 +582,7 @@ while (<>) { s;\bsmtpd_sasl_authenticated_header\b;$&;g; s;\bsmtpd_sasl_exceptions_networks\b;$&;g; s;\bsmtpd_sasl_local_domain\b;$&;g; + s;\bsmtpd_sasl_response_limit\b;$&;g; s;\bsmtpd_sasl_secu[-]*\n* *[]*rity_options\b;$&;g; s;\bsmtpd_sender_login_maps\b;$&;g; s;\bsmtpd_sender_restrictions\b;$&;g; diff --git a/postfix/proto/SMTPD_POLICY_README.html b/postfix/proto/SMTPD_POLICY_README.html index 79c876413..ebda427ba 100644 --- a/postfix/proto/SMTPD_POLICY_README.html +++ b/postfix/proto/SMTPD_POLICY_README.html @@ -223,6 +223,8 @@ server_port=54321 DATA, END-OF-MESSAGE, VRFY or ETRN; these are the SMTP protocol states where the Postfix SMTP server makes an OK/REJECT/HOLD/etc. decision. + The DATA protocol state also applies to email that is received + with BDAT commands (Postfix 3.4 and later).

diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index 271c701e3..7e169b6b2 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -10779,6 +10779,22 @@ selected with smtpd_sasl_type and smtpd_sasl_path.

This feature is available in Postfix 2.11 and later. Prior versions behave as if "smtp" is specified.

+%PARAM smtpd_sasl_response_limit 12288 + +

The maximum length of a SASL client's response to a server challenge. +When the client's "initial response" is longer than the normal limit for +SMTP commands, the client must omit its initial response, and wait for an +empty server challenge; it can then send what would have been its "initial +response" as a response to the empty server challenge. RFC4954 requires the +server to accept client responses up to at least 12288 octets of +base64-encoded text. The default value is therefore also the minimum value +accepted for this parameter.

+ +

This feature is available in Postfix 3.4 and later. Prior versions use +"line_length_limit", which may need to be raised to accomodate larger client +responses, as may be needed with GSSAPI authenticaiton of Windows AD users +who are members of many groups.

+ %PARAM cyrus_sasl_config_path

Search path for Cyrus SASL application configuration files, diff --git a/postfix/src/global/dict_mysql.c b/postfix/src/global/dict_mysql.c index 77adb42a4..d90bc090d 100644 --- a/postfix/src/global/dict_mysql.c +++ b/postfix/src/global/dict_mysql.c @@ -325,6 +325,19 @@ static const char *dict_mysql_lookup(DICT *dict, const char *name) dict->error = 0; + /* + * Don't frustrate future attempts to make Postfix UTF-8 transparent. + */ +#ifdef SNAPSHOT + if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0 + && !valid_utf8_string(name, strlen(name))) { + if (msg_verbose) + msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'", + myname, dict_mysql->parser->name, name); + return (0); + } +#endif + /* * Optionally fold the key. */ diff --git a/postfix/src/global/dict_pgsql.c b/postfix/src/global/dict_pgsql.c index 0ffc75586..8eac25606 100644 --- a/postfix/src/global/dict_pgsql.c +++ b/postfix/src/global/dict_pgsql.c @@ -190,7 +190,7 @@ typedef struct { char *hostname; char *name; char *port; - unsigned type; /* TYPEUNIX | TYPEINET | TYPECONNSTRING*/ + unsigned type; /* TYPEUNIX | TYPEINET | TYPECONNSTRING */ unsigned stat; /* STATUNTRIED | STATFAIL | STATCUR */ time_t ts; /* used for attempting reconnection */ } HOST; @@ -345,6 +345,19 @@ static const char *dict_pgsql_lookup(DICT *dict, const char *name) dict->error = 0; + /* + * Don't frustrate future attempts to make Postfix UTF-8 transparent. + */ +#ifdef SNAPSHOT + if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0 + && !valid_utf8_string(name, strlen(name))) { + if (msg_verbose) + msg_info("%s: %s: Skipping lookup of non-UTF-8 key '%s'", + myname, dict_pgsql->parser->name, name); + return (0); + } +#endif + /* * Optionally fold the key. */ @@ -643,6 +656,18 @@ static void plpgsql_connect_single(HOST *host, char *dbname, char *username, cha msg_info("dict_pgsql: successful connection to host %s", host->hostname); + /* + * The only legitimate encodings for Internet mail are ASCII and UTF-8. + */ +#ifdef SNAPSHOT + if (PQsetClientEncoding(host->db, "UTF8") != 0) { + msg_warn("dict_pgsql: cannot set the encoding to UTF8, skipping %s", + host->hostname); + plpgsql_down_host(host); + return; + } +#else + /* * XXX Postfix does not send multi-byte characters. The following piece * of code is an explicit statement of this fact, and the database server @@ -654,6 +679,7 @@ static void plpgsql_connect_single(HOST *host, char *dbname, char *username, cha plpgsql_down_host(host); return; } +#endif /* Success. */ host->stat = STATACTIVE; } diff --git a/postfix/src/global/ehlo_mask.c b/postfix/src/global/ehlo_mask.c index 1671beb3e..7ebcb9c2c 100644 --- a/postfix/src/global/ehlo_mask.c +++ b/postfix/src/global/ehlo_mask.c @@ -19,6 +19,7 @@ /* #define EHLO_MASK_ENHANCEDSTATUSCODES (1<<10) /* #define EHLO_MASK_DSN (1<<11) /* #define EHLO_MASK_SMTPUTF8 (1<<12) +/* #define EHLO_MASK_CHUNKING (1<<13) /* #define EHLO_MASK_SILENT (1<<15) /* /* int ehlo_mask(keyword_list) @@ -77,6 +78,7 @@ static const NAME_MASK ehlo_mask_table[] = { "ENHANCEDSTATUSCODES", EHLO_MASK_ENHANCEDSTATUSCODES, "DSN", EHLO_MASK_DSN, "EHLO_MASK_SMTPUTF8", EHLO_MASK_SMTPUTF8, + "CHUNKING", EHLO_MASK_CHUNKING, "SILENT-DISCARD", EHLO_MASK_SILENT, /* XXX In-band signaling */ 0, }; diff --git a/postfix/src/global/ehlo_mask.h b/postfix/src/global/ehlo_mask.h index 3ef2a2d87..ed0f7dc02 100644 --- a/postfix/src/global/ehlo_mask.h +++ b/postfix/src/global/ehlo_mask.h @@ -28,6 +28,7 @@ #define EHLO_MASK_ENHANCEDSTATUSCODES (1<<10) #define EHLO_MASK_DSN (1<<11) #define EHLO_MASK_SMTPUTF8 (1<<12) +#define EHLO_MASK_CHUNKING (1<<13) #define EHLO_MASK_SILENT (1<<15) extern int ehlo_mask(const char *); diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h index 5b92c1c68..8a4ae583e 100644 --- a/postfix/src/global/mail_params.h +++ b/postfix/src/global/mail_params.h @@ -1664,6 +1664,19 @@ extern char *var_smtpd_snd_auth_maps; #define REJECT_UNAUTH_SENDER_LOGIN_MISMATCH \ "reject_unauthenticated_sender_login_mismatch" + /* + * https://tools.ietf.org/html/rfc4954#page-5 + * + * (At the time of writing of this document, 12288 octets is considered to be a + * sufficient line length limit for handling of deployed authentication + * mechanisms.) + * + * The default value is also the minimum permissible value for this parameter. + */ +#define VAR_SMTPD_SASL_RESP_LIMIT "smtpd_sasl_response_limit" +#define DEF_SMTPD_SASL_RESP_LIMIT 12288 +extern int var_smtpd_sasl_resp_limit; + /* * SASL authentication support, SMTP client side. */ diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 020e9bc22..4821e002f 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -20,7 +20,7 @@ * Patches change both the patchlevel and the release date. Snapshots have no * patchlevel; they change the release date only. */ -#define MAIL_RELEASE_DATE "20180823" +#define MAIL_RELEASE_DATE "20180826" #define MAIL_VERSION_NUMBER "3.4" #ifdef SNAPSHOT diff --git a/postfix/src/global/smtp_stream.c b/postfix/src/global/smtp_stream.c index b9f1c506f..367dcb1d4 100644 --- a/postfix/src/global/smtp_stream.c +++ b/postfix/src/global/smtp_stream.c @@ -37,6 +37,11 @@ /* ssize_t len; /* VSTREAM *stream; /* +/* void smtp_fread(vp, len, stream) +/* VSTRING *vp; +/* ssize_t len; +/* VSTREAM *stream; +/* /* void smtp_fputc(ch, stream) /* int ch; /* VSTREAM *stream; @@ -45,6 +50,12 @@ /* VSTREAM *stream; /* char *format; /* va_list ap; +/* AUXILIARY API +/* int smtp_get_noexcept(vp, stream, maxlen, flags) +/* VSTRING *vp; +/* VSTREAM *stream; +/* ssize_t maxlen; +/* int flags; /* LEGACY API /* void smtp_timeout_setup(stream, timeout) /* VSTREAM *stream; @@ -82,10 +93,15 @@ /* and protects the program against running out of memory. /* Specify a zero bound to turn off bounds checking. /* The result is the last character read, or VSTREAM_EOF. -/* The \fIflags\fR argument is either SMTP_GET_FLAG_NONE (no -/* special processing) or SMTP_GET_FLAG_SKIP (skip over input -/* in excess of \fImaxlen\fR). Either way, a result value of -/* '\n' means that the input did not exceed \fImaxlen\fR. +/* The \fIflags\fR argument is zero or more of: +/* .RS +/* .IP SMTP_GET_FLAG_SKIP +/* Skip over input in excess of \fImaxlen\fR). Either way, a result +/* value of '\n' means that the input did not exceed \fImaxlen\fR. +/* .IP SMTP_GET_FLAG_APPEND +/* Append content to the buffer instead of overwriting it. +/* .RE +/* Specify SMTP_GET_FLAG_NONE for no special processing. /* /* smtp_fputs() writes its string argument to the named stream. /* Long strings are not broken. Each string is followed by a @@ -95,11 +111,18 @@ /* Long strings are not broken. No CR LF is appended. The stream /* is not flushed. /* +/* smtp_fread() appends the specified number of bytes from the +/* stream to the buffer. The result is not null-terminated. +/* /* smtp_fputc() writes one character to the named stream. /* The stream is not flushed. /* /* smtp_vprintf() is the machine underneath smtp_printf(). /* +/* smtp_get_noexcept() implements the subset of smtp_get() +/* without timeouts and without making long jumps. Instead, +/* query the stream status with vstream_feof() etc. +/* /* smtp_timeout_setup() is a backwards-compatibility interface /* for programs that don't require per-record deadline support. /* DIAGNOSTICS @@ -303,6 +326,29 @@ int smtp_fgetc(VSTREAM *stream) /* smtp_get - read one line from SMTP peer */ int smtp_get(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags) +{ + int last_char; + + /* + * Do the I/O, protected against timeout. + */ + smtp_timeout_reset(stream); + last_char = smtp_get_noexcept(vp, stream, bound, flags); + + /* + * EOF is bad, whether or not it happens in the middle of a record. Don't + * allow data that was truncated because of EOF. + */ + if (vstream_ftimeout(stream)) + smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_get"); + if (vstream_feof(stream) || vstream_ferror(stream)) + smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_get"); + return (last_char); +} + +/* smtp_get_noexcept - read one line from SMTP peer, without exceptions */ + +int smtp_get_noexcept(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags) { int last_char; int next_char; @@ -316,9 +362,13 @@ int smtp_get(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags) * XXX 2821: Section 4.1.1.4 says that an SMTP server must not recognize * bare LF as record terminator. */ - smtp_timeout_reset(stream); - last_char = (bound == 0 ? vstring_get(vp, stream) : - vstring_get_bound(vp, stream, bound)); + last_char = (bound == 0 ? + vstring_get_flags(vp, stream, + (flags & SMTP_GET_FLAG_APPEND) ? + VSTRING_GET_FLAG_APPEND : 0) : + vstring_get_flags_bound(vp, stream, + (flags & SMTP_GET_FLAG_APPEND) ? + VSTRING_GET_FLAG_APPEND : 0, bound)); switch (last_char) { @@ -367,14 +417,6 @@ int smtp_get(VSTRING *vp, VSTREAM *stream, ssize_t bound, int flags) && next_char != '\n') /* void */ ; - /* - * EOF is bad, whether or not it happens in the middle of a record. Don't - * allow data that was truncated because of EOF. - */ - if (vstream_ftimeout(stream)) - smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_get"); - if (vstream_feof(stream) || vstream_ferror(stream)) - smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_get"); return (last_char); } @@ -427,6 +469,33 @@ void smtp_fwrite(const char *cp, ssize_t todo, VSTREAM *stream) smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fwrite"); } +/* smtp_fread - read one buffer from SMTP peer */ + +void smtp_fread(VSTRING *vp, ssize_t todo, VSTREAM *stream) +{ + int err; + + if (todo <= 0) + msg_panic("smtp_fread: zero or negative todo %ld", (long) todo); + + /* + * Do the I/O, protected against timeout. + */ + smtp_timeout_reset(stream); + VSTRING_SPACE(vp, todo); + err = (vstream_fread(stream, vstring_end(vp), todo) != todo); + if (err == 0) + VSTRING_AT_OFFSET(vp, VSTRING_LEN(vp) + todo); + + /* + * See if there was a problem. + */ + if (vstream_ftimeout(stream)) + smtp_longjmp(stream, SMTP_ERR_TIME, "smtp_fread"); + if (err != 0) + smtp_longjmp(stream, SMTP_ERR_EOF, "smtp_fread"); +} + /* smtp_fputc - write to SMTP peer */ void smtp_fputc(int ch, VSTREAM *stream) diff --git a/postfix/src/global/smtp_stream.h b/postfix/src/global/smtp_stream.h index bdb143675..cd2bfce69 100644 --- a/postfix/src/global/smtp_stream.h +++ b/postfix/src/global/smtp_stream.h @@ -38,8 +38,10 @@ extern void PRINTFLIKE(2, 3) smtp_printf(VSTREAM *, const char *,...); extern void smtp_flush(VSTREAM *); extern int smtp_fgetc(VSTREAM *); extern int smtp_get(VSTRING *, VSTREAM *, ssize_t, int); +extern int smtp_get_noexcept(VSTRING *, VSTREAM *, ssize_t, int); extern void smtp_fputs(const char *, ssize_t len, VSTREAM *); extern void smtp_fwrite(const char *, ssize_t len, VSTREAM *); +extern void smtp_fread(VSTRING *, ssize_t len, VSTREAM *); extern void smtp_fputc(int, VSTREAM *); extern void smtp_vprintf(VSTREAM *, const char *, va_list); @@ -49,6 +51,7 @@ extern void smtp_vprintf(VSTREAM *, const char *, va_list); #define SMTP_GET_FLAG_NONE 0 #define SMTP_GET_FLAG_SKIP (1<<0) /* skip over excess input */ +#define SMTP_GET_FLAG_APPEND (1<<1) /* append instead of overwrite */ /* LICENSE /* .ad diff --git a/postfix/src/postscreen/postscreen.c b/postfix/src/postscreen/postscreen.c index 4062a9b2d..af6bff303 100644 --- a/postfix/src/postscreen/postscreen.c +++ b/postfix/src/postscreen/postscreen.c @@ -61,6 +61,7 @@ /* RFC 2034 (SMTP Enhanced Status Codes) /* RFC 2821 (SMTP protocol) /* Not: RFC 2920 (SMTP Pipelining) +/* RFC 3030 (CHUNKING without BINARYMIME) /* RFC 3207 (STARTTLS command) /* RFC 3461 (SMTP DSN Extension) /* RFC 3463 (Enhanced Status Codes) diff --git a/postfix/src/postscreen/postscreen_smtpd.c b/postfix/src/postscreen/postscreen_smtpd.c index ee3d12b1a..50148917a 100644 --- a/postfix/src/postscreen/postscreen_smtpd.c +++ b/postfix/src/postscreen/postscreen_smtpd.c @@ -267,13 +267,13 @@ static DICT *psc_cmd_filter; PSC_CLEAR_EVENT_REQUEST(vstream_fileno((state)->smtp_client_stream), \ (event), (void *) (state)); \ PSC_DROP_SESSION_STATE((state), (reply)); \ - } while (0); + } while (0) #define PSC_CLEAR_EVENT_HANGUP(state, event) do { \ PSC_CLEAR_EVENT_REQUEST(vstream_fileno((state)->smtp_client_stream), \ (event), (void *) (state)); \ psc_hangup_event(state); \ - } while (0); + } while (0) /* psc_helo_cmd - record HELO and respond */ @@ -345,6 +345,8 @@ static void psc_smtpd_format_ehlo_reply(VSTRING *buf, int discard_mask /* Fix 20140708: announce SMTPUTF8. */ if (var_smtputf8_enable && (discard_mask & EHLO_MASK_SMTPUTF8) == 0) PSC_EHLO_APPEND(saved_len, psc_temp, "250-SMTPUTF8\r\n"); + if ((discard_mask & EHLO_MASK_CHUNKING) == 0) + PSC_EHLO_APPEND(saved_len, psc_temp, "250-CHUNKING\r\n"); STR(psc_temp)[saved_len + 3] = ' '; } @@ -581,27 +583,61 @@ static int psc_rcpt_cmd(PSC_STATE *state, char *args) static int psc_data_cmd(PSC_STATE *state, char *args) { + const char myname[] = "psc_data_cmd"; /* - * smtpd(8) incompatibility: we reject all requests. + * smtpd(8) incompatibility: postscreen(8) drops the connection, instead + * of waiting for the next command. Justification: postscreen(8) should + * never see DATA from a legitimate client, because 1) the server rejects + * every recipient, and 2) the server does not announce PIPELINING. */ if (PSC_SMTPD_NEXT_TOKEN(args) != 0) - return (PSC_SEND_REPLY(state, - "501 5.5.4 Syntax: DATA\r\n")); - if (state->sender == 0) - return (PSC_SEND_REPLY(state, - "503 5.5.1 Error: need RCPT command\r\n")); + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "501 5.5.4 Syntax: DATA\r\n"); + else if (state->sender == 0) + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "503 5.5.1 Error: need RCPT command\r\n"); + else + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "554 5.5.1 Error: no valid recipients\r\n"); + /* Caution: state is now a dangling pointer. */ + return (0); +} + +/* psc_bdat_cmd - respond to BDAT and disconnect */ + +static int psc_bdat_cmd(PSC_STATE *state, char *args) +{ + const char *myname = "psc_bdat_cmd"; /* - * We really would like to hang up the connection as early as possible, - * so that we dont't have to deal with broken zombies that fall silent at - * the first reject response. For now we rely on stress-dependent command - * read timeouts. - * - * If we proceed into the data phase, enforce over-all DATA time limit. + * smtpd(8) incompatibility: postscreen(8) drops the connection, instead + * of reading the entire BDAT chunk and staying in sync with the client. + * Justification: postscreen(8) should never see BDAT from a legitimate + * client, because 1) the server rejects every recipient, and 2) the + * server does not announce PIPELINING. */ - return (PSC_SEND_REPLY(state, - "554 5.5.1 Error: no valid recipients\r\n")); + if (state->ehlo_discard_mask & EHLO_MASK_CHUNKING) + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "502 5.5.1 Error: command not implemented\r\n"); + else if (PSC_SMTPD_NEXT_TOKEN(args) == 0) + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "501 5.5.4 Syntax: BDAT count [LAST]\r\n"); + else if (state->sender == 0) + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "554 5.5.1 Error: need RCPT command\r\n"); + else + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "554 5.5.1 Error: no valid recipients\r\n"); + /* Caution: state is now a dangling pointer. */ + return (0); } /* psc_rset_cmd - reset, send 250 OK */ @@ -711,8 +747,9 @@ static const PSC_SMTPD_COMMAND command_table[] = { "AUTH", psc_noop_cmd, PSC_SMTPD_CMD_FLAG_NONE, "MAIL", psc_mail_cmd, PSC_SMTPD_CMD_FLAG_ENABLE, "RCPT", psc_rcpt_cmd, PSC_SMTPD_CMD_FLAG_ENABLE, - "DATA", psc_data_cmd, PSC_SMTPD_CMD_FLAG_ENABLE, + "DATA", psc_data_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_DESTROY, /* ".", psc_dot_cmd, PSC_SMTPD_CMD_FLAG_NONE, */ + "BDAT", psc_bdat_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_DESTROY, "RSET", psc_rset_cmd, PSC_SMTPD_CMD_FLAG_ENABLE, "NOOP", psc_noop_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS, "VRFY", psc_vrfy_cmd, PSC_SMTPD_CMD_FLAG_ENABLE, diff --git a/postfix/src/smtp/smtp_sasl_glue.c b/postfix/src/smtp/smtp_sasl_glue.c index d2c1c3c59..f86bcd9e2 100644 --- a/postfix/src/smtp/smtp_sasl_glue.c +++ b/postfix/src/smtp/smtp_sasl_glue.c @@ -358,22 +358,36 @@ int smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why) session->namaddr, STR(session->sasl_reply)); return (-1); } - - /* + /*- * Send the AUTH command and the optional initial client response. - * sasl_encode64() produces four bytes for each complete or incomplete - * triple of input bytes. Allocate an extra byte for string termination. + * + * https://tools.ietf.org/html/rfc4954#page-4 + * Note that the AUTH command is still subject to the line length + * limitations defined in [SMTP]. If use of the initial response argument + * would cause the AUTH command to exceed this length, the client MUST NOT + * use the initial response parameter... + * + * https://tools.ietf.org/html/rfc5321#section-4.5.3.1.4 + * The maximum total length of a command line including the command word + * and the is 512 octets. + * + * Defer the initial response if the resulting command exceeds the limit. */ - if (LEN(session->sasl_reply) > 0) { + if (LEN(session->sasl_reply) > 0 + && strlen(mechanism) + LEN(session->sasl_reply) + 4 <= 512) { smtp_chat_cmd(session, "AUTH %s %s", mechanism, STR(session->sasl_reply)); + VSTRING_RESET(session->sasl_reply); /* no deferred initial reply */ } else { smtp_chat_cmd(session, "AUTH %s", mechanism); } /* * Step through the authentication protocol until the server tells us - * that we are done. + * that we are done. If session->sasl_reply is non-empty we have a + * deferred initial reply and expect an empty initial challenge from the + * server. If the server's initial challenge is non-empty we have a SASL + * protocol violation with both sides wanting to go first. */ while ((resp = smtp_chat_resp(session))->code / 100 == 3) { @@ -392,21 +406,39 @@ int smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why) */ line = resp->str; (void) mystrtok(&line, "- \t\n"); /* skip over result code */ - result = xsasl_client_next(session->sasl_client, line, - session->sasl_reply); - if (result != XSASL_AUTH_OK) { - dsb_update(why, "4.7.0", DSB_DEF_ACTION, /* Fix 200512 */ + + if (LEN(session->sasl_reply) > 0) { + + /* + * Deferred initial response, the server challenge must be empty. + * Cleared after actual transmission to the server. + */ + if (*line) { + dsb_update(why, "4.7.0", DSB_DEF_ACTION, + DSB_SKIP_RMTA, DSB_DTYPE_SASL, "protocol error", + "SASL authentication failed; non-empty initial " + "%s challenge from server %s: %s", mechanism, + session->namaddr, STR(session->sasl_reply)); + return (-1); + } + } else { + result = xsasl_client_next(session->sasl_client, line, + session->sasl_reply); + if (result != XSASL_AUTH_OK) { + dsb_update(why, "4.7.0", DSB_DEF_ACTION, /* Fix 200512 */ DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply), - "SASL authentication failed; " - "cannot authenticate to server %s: %s", - session->namaddr, STR(session->sasl_reply)); - return (-1); /* Fix 200512 */ + "SASL authentication failed; " + "cannot authenticate to server %s: %s", + session->namaddr, STR(session->sasl_reply)); + return (-1); /* Fix 200512 */ + } } /* * Send a client response. */ smtp_chat_cmd(session, "%s", STR(session->sasl_reply)); + VSTRING_RESET(session->sasl_reply); /* clear initial reply */ } /* diff --git a/postfix/src/smtpd/Makefile.in b/postfix/src/smtpd/Makefile.in index ac3d38f1a..14540d370 100644 --- a/postfix/src/smtpd/Makefile.in +++ b/postfix/src/smtpd/Makefile.in @@ -264,6 +264,7 @@ smtpd_chat.o: ../../include/argv.h smtpd_chat.o: ../../include/attr.h smtpd_chat.o: ../../include/check_arg.h smtpd_chat.o: ../../include/cleanup_user.h +smtpd_chat.o: ../../include/dict.h smtpd_chat.o: ../../include/dns.h smtpd_chat.o: ../../include/htable.h smtpd_chat.o: ../../include/int_filt.h @@ -280,6 +281,7 @@ smtpd_chat.o: ../../include/maps.h smtpd_chat.o: ../../include/milter.h smtpd_chat.o: ../../include/msg.h smtpd_chat.o: ../../include/myaddrinfo.h +smtpd_chat.o: ../../include/myflock.h smtpd_chat.o: ../../include/mymalloc.h smtpd_chat.o: ../../include/name_code.h smtpd_chat.o: ../../include/name_mask.h diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c index 99284b3be..009f77f5d 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -46,6 +46,7 @@ /* RFC 2554 (AUTH command) /* RFC 2821 (SMTP protocol) /* RFC 2920 (SMTP pipelining) +/* RFC 3030 (CHUNKING without BINARYMIME) /* RFC 3207 (STARTTLS command) /* RFC 3461 (SMTP DSN extension) /* RFC 3463 (Enhanced status codes) @@ -338,6 +339,10 @@ /* .IP "\fBsmtpd_sasl_service (smtp)\fR" /* The service name that is passed to the SASL plug-in that is /* selected with \fBsmtpd_sasl_type\fR and \fBsmtpd_sasl_path\fR. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBsmtpd_sasl_response_limit (12288)\fR" +/* The maximum length of a SASL client's response to a server challenge. /* STARTTLS SUPPORT CONTROLS /* .ad /* .fi @@ -1283,6 +1288,7 @@ char *var_smtpd_sasl_path; char *var_smtpd_sasl_service; char *var_cyrus_conf_path; char *var_smtpd_sasl_realm; +int var_smtpd_sasl_resp_limit; char *var_smtpd_sasl_exceptions_networks; char *var_smtpd_sasl_type; char *var_filter_xport; @@ -1912,6 +1918,8 @@ static int ehlo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) EHLO_APPEND(state, "DSN"); if (var_smtputf8_enable && (discard_mask & EHLO_MASK_SMTPUTF8) == 0) EHLO_APPEND(state, "SMTPUTF8"); + if ((discard_mask & EHLO_MASK_CHUNKING) == 0) + EHLO_APPEND(state, "CHUNKING"); /* * Send the reply. @@ -2399,6 +2407,12 @@ static int mail_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) smtpd_chat_reply(state, "503 5.5.1 Error: nested MAIL command"); return (-1); } + /* Don't accept MAIL after out-of-order BDAT. */ + if (SMTPD_PROCESSING_BDAT(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: MAIL after BDAT"); + return (-1); + } if (argc < 3 || strcasecmp(argv[1].strval, "from:") != 0) { state->error_mask |= MAIL_ERROR_PROTOCOL; @@ -2740,6 +2754,17 @@ static void mail_reset(SMTPD_STATE *state) state->milter_argv = 0; state->milter_argc = 0; } + + /* + * BDAT. + */ + state->bdat_state = SMTPD_BDAT_STAT_NONE; + if (state->bdat_get_stream) { + (void) vstream_fclose(state->bdat_get_stream); + state->bdat_get_stream = 0; + } + if (state->bdat_get_buffer) + VSTRING_RESET(state->bdat_get_buffer); } /* rcpt_cmd - process RCPT TO command */ @@ -2772,6 +2797,12 @@ static int rcpt_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) smtpd_chat_reply(state, "503 5.5.1 Error: need MAIL command"); return (-1); } + /* Don't accept RCPT after BDAT. */ + if (SMTPD_PROCESSING_BDAT(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: RCPT after BDAT"); + return (-1); + } if (argc < 3 || strcasecmp(argv[1].strval, "to:") != 0) { state->error_mask |= MAIL_ERROR_PROTOCOL; @@ -3121,45 +3152,37 @@ static void comment_sanitize(VSTRING *comment_string) VSTRING_TERMINATE(comment_string); } +static void common_pre_message_handling(SMTPD_STATE *state, + int (*out_record) (VSTREAM *, int, const char *, ssize_t), + int (*out_fprintf) (VSTREAM *, int, const char *,...), + VSTREAM *out_stream, int out_error); +static void receive_data_message(SMTPD_STATE *state, + int (*out_record) (VSTREAM *, int, const char *, ssize_t), + int (*out_fprintf) (VSTREAM *, int, const char *,...), + VSTREAM *out_stream, int out_error); +static int common_post_message_handling(SMTPD_STATE *state); + /* data_cmd - process DATA command */ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) { SMTPD_PROXY *proxy; const char *err; - char *start; - int len; - int curr_rec_type; - int prev_rec_type; - int first = 1; - VSTRING *why = 0; - int saved_err; int (*out_record) (VSTREAM *, int, const char *, ssize_t); int (*out_fprintf) (VSTREAM *, int, const char *,...); VSTREAM *out_stream; int out_error; - char **cpp; - const CLEANUP_STAT_DETAIL *detail; - const char *rfc3848_sess; - const char *rfc3848_auth; - const char *with_protocol = (state->flags & SMTPD_FLAG_SMTPUTF8) ? - "UTF8SMTP" : state->protocol; - -#ifdef USE_TLS - VSTRING *peer_CN; - VSTRING *issuer_CN; - -#endif -#ifdef USE_SASL_AUTH - VSTRING *username; - -#endif /* * Sanity checks. With ESMTP command pipelining the client can send DATA * before all recipients are rejected, so don't report that as a protocol * error. */ + if (SMTPD_PROCESSING_BDAT(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: DATA after BDAT"); + return (-1); + } if (state->rcpt_count == 0) { if (!SMTPD_IN_MAIL_TRANSACTION(state)) { state->error_mask |= MAIL_ERROR_PROTOCOL; @@ -3208,6 +3231,38 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) out_fprintf = rec_fprintf; out_error = CLEANUP_STAT_WRITE; } + common_pre_message_handling(state, out_record, out_fprintf, + out_stream, out_error); + smtpd_chat_reply(state, "354 End data with ."); + state->where = SMTPD_AFTER_DATA; + receive_data_message(state, out_record, out_fprintf, out_stream, out_error); + return common_post_message_handling(state); +} + +/* common_pre_message_handling - finish envelope and open message segment */ + +static void common_pre_message_handling(SMTPD_STATE *state, + int (*out_record) (VSTREAM *, int, const char *, ssize_t), + int (*out_fprintf) (VSTREAM *, int, const char *,...), + VSTREAM *out_stream, + int out_error) +{ + SMTPD_PROXY *proxy = state->proxy; + char **cpp; + const char *rfc3848_sess; + const char *rfc3848_auth; + const char *with_protocol = (state->flags & SMTPD_FLAG_SMTPUTF8) ? + "UTF8SMTP" : state->protocol; + +#ifdef USE_TLS + VSTRING *peer_CN; + VSTRING *issuer_CN; + +#endif +#ifdef USE_SASL_AUTH + VSTRING *username; + +#endif /* * Flush out a first batch of access table actions that are delegated to @@ -3327,8 +3382,22 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) "\t(envelope-from %s)", STR(state->buffer)); #endif } - smtpd_chat_reply(state, "354 End data with ."); - state->where = SMTPD_AFTER_DATA; +} + +/* receive_data_message - finish envelope and open message segment */ + +static void receive_data_message(SMTPD_STATE *state, + int (*out_record) (VSTREAM *, int, const char *, ssize_t), + int (*out_fprintf) (VSTREAM *, int, const char *,...), + VSTREAM *out_stream, + int out_error) +{ + SMTPD_PROXY *proxy = state->proxy; + char *start; + int len; + int curr_rec_type; + int prev_rec_type; + int first = 1; /* * Copy the message content. If the cleanup process has a problem, keep @@ -3375,7 +3444,19 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) } } } - state->where = SMTPD_AFTER_DOT; + state->where = SMTPD_AFTER_EOM; +} + +/* common_post_message_handling - commit message or report error */ + +static int common_post_message_handling(SMTPD_STATE *state) +{ + SMTPD_PROXY *proxy = state->proxy; + const char *err; + VSTRING *why = 0; + int saved_err; + const CLEANUP_STAT_DETAIL *detail; + if (state->err == CLEANUP_STAT_OK && SMTPD_STAND_ALONE(state) == 0 && (err = smtpd_check_eod(state)) != 0) { @@ -3492,6 +3573,10 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) state->junk_cmds = 0; if (proxy) smtpd_chat_reply(state, "%s", STR(proxy->reply)); + else if (SMTPD_PROCESSING_BDAT(state)) + smtpd_chat_reply(state, + "250 2.0.0 Ok: %ld bytes queued as %s", + (long) state->act_size, state->queue_id); else smtpd_chat_reply(state, "250 2.0.0 Ok: queued as %s", state->queue_id); @@ -3570,6 +3655,301 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) return (saved_err); } +/* skip_bdat - skip content and respond to BDAT error */ + +static int skip_bdat(SMTPD_STATE *state, off_t chunk_size, + bool final_chunk, const char *format,...) +{ + va_list ap; + off_t done; + off_t len; + + /* + * Read and discard content from the remote SMTP client. TODO: drop the + * connection in case of overload. + */ + for (done = 0; done < chunk_size; done += len) { + VSTRING_RESET(state->buffer); + if ((len = chunk_size - done) > VSTREAM_BUFSIZE) + len = VSTREAM_BUFSIZE; + smtp_fread(state->buffer, len, state->client); + } + + /* + * Send the response to the remote SMTP client. + */ + va_start(ap, format); + vsmtpd_chat_reply(state, format, ap); + va_end(ap); + + /* + * Reset state, or drop subsequent BDAT payloads until BDAT LAST or RSET. + */ + if (final_chunk) + mail_reset(state); + else + state->bdat_state = SMTPD_BDAT_STAT_ERROR; + return (-1); +} + +/* bdat_cmd - process BDAT command */ + +static int bdat_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + SMTPD_PROXY *proxy; + const char *err; + off_t chunk_size; + bool final_chunk; + off_t done; + off_t read_len; + char *start; + int len; + int curr_rec_type; + int (*out_record) (VSTREAM *, int, const char *, ssize_t); + int (*out_fprintf) (VSTREAM *, int, const char *,...); + VSTREAM *out_stream; + int out_error; + + /* + * Hang up if the BDAT command is disabled. The next input would be raw + * message content and that would trigger lots of command errors. + */ + if (state->ehlo_discard_mask & EHLO_MASK_CHUNKING) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "521 5.5.1 Error: command not implemented"); + return (-1); + } + + /* + * Hang up if the BDAT command is malformed. The next input would be raw + * message content and that would trigger lots of command errors. + */ + if (argc < 2 || argc > 3 || !alldig(argv[1].strval) + || (chunk_size = off_cvt_string(argv[1].strval)) < 0 + || ((final_chunk = (argc == 3)) + && strcasecmp(argv[2].strval, "LAST") != 0)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + msg_warn("%s: malformed BDAT command syntax from %s: %.100s", + state->queue_id ? state->queue_id : "NOQUEUE", + state->namaddr, printable(vstring_str(state->buffer), '?')); + smtpd_chat_reply(state, "521 5.5.4 Syntax: BDAT count [LAST]"); + return (-1); + } + + /* + * Block abuse involving empty chunks (alternatively, we could count + * "BDAT 0" as a "NOOP", but then we would have to refactor the code that + * enforces the junk command limit). Clients that send a message as a + * sequence of "BDAT 1" should not be a problem: the Postfix BDAT + * implementation should be efficient enough to handle that. + */ + if (chunk_size == 0 && !final_chunk) { + msg_warn("%s: null BDAT request from %s", + state->queue_id ? state->queue_id : "NOQUEUE", + state->namaddr); + return skip_bdat(state, chunk_size, final_chunk, + "551 5.7.1 Null BDAT request"); + } + + /* + * BDAT commands may be pipelined within a MAIL transaction. After a BDAT + * request fails, keep accepting BDAT requests and skipping BDAT payloads + * to maintain synchronization with the remote SMTP client, until the + * client sends BDAT LAST or RSET. + */ + if (state->bdat_state == SMTPD_BDAT_STAT_ERROR) + return skip_bdat(state, chunk_size, final_chunk, + "551 5.0.0 Discarded %ld bytes after earlier error", + (long) chunk_size); + + /* + * Special handling for the first BDAT command in a MAIL transaction, + * treating it as a kind of "DATA" command for the purpose of policy + * evaluation. + */ + if (!SMTPD_PROCESSING_BDAT(state)) { + + /* + * With ESMTP command pipelining a client may send BDAT before the + * server has replied to all RCPT commands. For this reason we cannot + * treat BDAT without valid recipients as a protocol error. Worse, + * RFC 3030 does not discuss the role of BDAT commands in RFC 2920 + * command groups (batches of commands that may be sent without + * waiting for a response to each individual command). Therefore we + * have to allow for clients that pipeline the entire SMTP session + * after EHLO, including multiple MAIL transactions. + */ + if (state->rcpt_count == 0) { + if (!SMTPD_IN_MAIL_TRANSACTION(state)) { + /* TODO: maybe remove this from the DATA and BDAT handlers. */ + state->error_mask |= MAIL_ERROR_PROTOCOL; + return skip_bdat(state, chunk_size, final_chunk, + "503 5.5.1 Error: need RCPT command"); + } else { + return skip_bdat(state, chunk_size, final_chunk, + "554 5.5.1 Error: no valid recipients"); + } + } + if (SMTPD_STAND_ALONE(state) == 0 + && (err = smtpd_check_data(state)) != 0) { + return skip_bdat(state, chunk_size, final_chunk, "%s", err); + } + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0 + && (err = milter_data_event(state->milters)) != 0 + && (err = check_milter_reply(state, err)) != 0) { + return skip_bdat(state, chunk_size, final_chunk, "%s", err); + } + proxy = state->proxy; + if (proxy != 0 && proxy->cmd(state, SMTPD_PROX_WANT_MORE, + SMTPD_CMD_DATA) != 0) { + return skip_bdat(state, chunk_size, final_chunk, + "%s", STR(proxy->reply)); + } + } + /* Block too large chunks. */ + if (state->act_size > var_message_limit - chunk_size) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("%s: BDAT request from %s exceeds message size limit", + state->queue_id ? state->queue_id : "NOQUEUE", + state->namaddr); + return skip_bdat(state, chunk_size, final_chunk, + "552 5.3.4 Chunk exceeds message size limit"); + } + + /* + * One level of indirection to choose between normal or proxied + * operation. We want to avoid massive code duplication within tons of + * if-else clauses. TODO: store this in its own data structure, or in + * SMTPD_STATE. + */ + proxy = state->proxy; + if (proxy) { + out_stream = proxy->stream; + out_record = proxy->rec_put; + out_fprintf = proxy->rec_fprintf; + out_error = CLEANUP_STAT_PROXY; + } else { + out_stream = state->cleanup; + out_record = rec_put; + out_fprintf = rec_fprintf; + out_error = CLEANUP_STAT_WRITE; + } + if (!SMTPD_PROCESSING_BDAT(state)) { + common_pre_message_handling(state, out_record, out_fprintf, + out_stream, out_error); + if (state->bdat_get_buffer == 0) + state->bdat_get_buffer = vstring_alloc(VSTREAM_BUFSIZE); + else + VSTRING_RESET(state->bdat_get_buffer); + state->bdat_prev_rec_type = 0; + } + state->bdat_state = SMTPD_BDAT_STAT_OK; + state->where = SMTPD_AFTER_BDAT; + + /* + * Copy the message content. If the cleanup process has a problem, keep + * reading until the remote stops sending, then complain. Produce typed + * records from the SMTP stream so we can handle data that spans buffers. + */ + + /* + * Instead of reading the entire BDAT chunk into memory, read the chunk + * one fragment at a time. The loops below always make one iteration, to + * avoid code duplication for the "BDAT 0 LAST" case (empty chunk). + */ + done = 0; + do { + if ((read_len = chunk_size - done) > VSTREAM_BUFSIZE) + read_len = VSTREAM_BUFSIZE; + /* Caution: smtp_fread() makes a long jump in case of EOF or timeout. */ + VSTRING_RESET(state->buffer); + if (read_len > 0) + smtp_fread(state->buffer, read_len, state->client); + state->bdat_get_stream = vstream_memreopen( + state->bdat_get_stream, state->buffer, O_RDONLY); + + /* + * Read lines from the fragment. The last line may continue in the + * next fragment, or in the next chunk. + */ + do { + if (smtp_get_noexcept(state->bdat_get_buffer, + state->bdat_get_stream, + var_line_limit, + SMTP_GET_FLAG_APPEND) == '\n') { + /* Stopped at end-of-line. */ + curr_rec_type = REC_TYPE_NORM; + } else if (!vstream_feof(state->bdat_get_stream)) { + /* Stopped at var_line_limit. */ + curr_rec_type = REC_TYPE_CONT; + } else if (VSTRING_LEN(state->bdat_get_buffer) > 0 + && final_chunk && read_len == chunk_size - done) { + /* Stopped at final chunk end; handle missing end-of-line. */ + curr_rec_type = REC_TYPE_NORM; + } else { + /* Stopped at fragment end; empty buffer or not at chunk end. */ + /* Skip the out_record() and VSTRING_RESET() calls below. */ + break; + } + start = vstring_str(state->bdat_get_buffer); + len = VSTRING_LEN(state->bdat_get_buffer); + if (state->err == CLEANUP_STAT_OK) { + if (var_message_limit > 0 + && var_message_limit - state->act_size < len + 2) { + state->err = CLEANUP_STAT_SIZE; + msg_warn("%s: queue file size limit exceeded", + state->queue_id ? state->queue_id : "NOQUEUE"); + } else { + state->act_size += len + 2; + if (*start == '.' && proxy != 0 + && state->bdat_prev_rec_type != REC_TYPE_CONT) + if (out_record(out_stream, REC_TYPE_CONT, ".", 1) < 0) + state->err = out_error; + if (state->err == CLEANUP_STAT_OK + && out_record(out_stream, curr_rec_type, + vstring_str(state->bdat_get_buffer), + VSTRING_LEN(state->bdat_get_buffer)) < 0) + state->err = out_error; + } + } + VSTRING_RESET(state->bdat_get_buffer); + state->bdat_prev_rec_type = curr_rec_type; + } while (!vstream_feof(state->bdat_get_stream)); + done += read_len; + } while (done < chunk_size); + + /* + * Special handling for BDAT LAST (successful or unsuccessful). + */ + if (final_chunk) { + state->where = SMTPD_AFTER_EOM; + return common_post_message_handling(state); + } + + /* + * Unsuccessful non-final BDAT command. common_post_message_handling() + * resets all MAIL transaction state including BDAT state. To avoid + * useless error messages due to pipelined BDAT commands, enter the + * SMTPD_BDAT_STAT_ERROR state to accept BDAT commands and skip BDAT + * payloads. + */ + else if (state->err != CLEANUP_STAT_OK) { + /* NOT: state->where = SMTPD_AFTER_EOM; */ + (void) common_post_message_handling(state); + state->bdat_state = SMTPD_BDAT_STAT_ERROR; + return (-1); + } + + /* + * Successful non-final BDAT command. + */ + else { + smtpd_chat_reply(state, "250 2.0.0 Ok: %ld bytes", (long) chunk_size); + return (0); + } +} + /* rset_cmd - process RSET */ static int rset_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) @@ -4842,6 +5222,13 @@ typedef struct SMTPD_CMD { int total_count; } SMTPD_CMD; + /* + * Per RFC 2920: "In particular, the commands RSET, MAIL FROM, SEND FROM, + * SOML FROM, SAML FROM, and RCPT TO can all appear anywhere in a pipelined + * command group. The EHLO, DATA, VRFY, EXPN, TURN, QUIT, and NOOP commands + * can only appear as the last command in a group". RFC 3030 allows BDAT + * commands to be pipelined as well. + */ #define SMTPD_CMD_FLAG_LIMIT (1<<0) /* limit usage */ #define SMTPD_CMD_FLAG_PRE_TLS (1<<1) /* allow before STARTTLS */ #define SMTPD_CMD_FLAG_LAST (1<<2) /* last in PIPELINING command group */ @@ -4864,6 +5251,7 @@ static SMTPD_CMD smtpd_cmd_table[] = { {SMTPD_CMD_MAIL, mail_cmd,}, {SMTPD_CMD_RCPT, rcpt_cmd,}, {SMTPD_CMD_DATA, data_cmd, SMTPD_CMD_FLAG_LAST,}, + {SMTPD_CMD_BDAT, bdat_cmd,}, {SMTPD_CMD_RSET, rset_cmd, SMTPD_CMD_FLAG_LIMIT,}, {SMTPD_CMD_NOOP, noop_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_PRE_TLS | SMTPD_CMD_FLAG_LAST,}, {SMTPD_CMD_VRFY, vrfy_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_LAST,}, @@ -5292,7 +5680,13 @@ static void smtpd_proto(SMTPD_STATE *state) state->reason, SMTPD_CMD_DATA, /* 2.5 compat */ (long) (state->act_size + vstream_peek(state->client)), state->namaddr); - } else if (strcmp(state->where, SMTPD_AFTER_DOT) + } else if (strcmp(state->where, SMTPD_AFTER_BDAT) == 0) { + msg_info("%s after %s (%lu bytes) from %s", + state->reason, SMTPD_CMD_BDAT, + (long) (state->act_size + VSTRING_LEN(state->buffer) + + VSTRING_LEN(state->bdat_get_buffer)), + state->namaddr); + } else if (strcmp(state->where, SMTPD_AFTER_EOM) || strcmp(state->reason, REASON_LOST_CONNECTION)) { msg_info("%s after %s from %s", state->reason, state->where, state->namaddr); @@ -5820,6 +6214,7 @@ int main(int argc, char **argv) #ifdef USE_TLS VAR_SMTPD_TLS_CCERT_VD, DEF_SMTPD_TLS_CCERT_VD, &var_smtpd_tls_ccert_vd, 0, 0, #endif + VAR_SMTPD_SASL_RESP_LIMIT, DEF_SMTPD_SASL_RESP_LIMIT, &var_smtpd_sasl_resp_limit, DEF_SMTPD_SASL_RESP_LIMIT, 0, VAR_SMTPD_POLICY_REQ_LIMIT, DEF_SMTPD_POLICY_REQ_LIMIT, &var_smtpd_policy_req_limit, 0, 0, VAR_SMTPD_POLICY_TRY_LIMIT, DEF_SMTPD_POLICY_TRY_LIMIT, &var_smtpd_policy_try_limit, 1, 0, 0, diff --git a/postfix/src/smtpd/smtpd.h b/postfix/src/smtpd/smtpd.h index 4facfb080..a584a2c83 100644 --- a/postfix/src/smtpd/smtpd.h +++ b/postfix/src/smtpd/smtpd.h @@ -182,13 +182,24 @@ typedef struct { const char **milter_argv; /* SMTP command vector */ ssize_t milter_argc; /* SMTP command vector */ const char *milter_reject_text; /* input to call-back from Milter */ - MILTERS *milters; /* Milter initialization status.*/ + MILTERS *milters; /* Milter initialization status. */ /* * EHLO temporary space. */ VSTRING *ehlo_buf; ARGV *ehlo_argv; + + /* + * BDAT processing state. + */ +#define SMTPD_BDAT_STAT_NONE 0 /* not processing BDAT */ +#define SMTPD_BDAT_STAT_OK 1 /* accepting BDAT chunks */ +#define SMTPD_BDAT_STAT_ERROR 2 /* skipping BDAT chunks */ + int bdat_state; /* see above */ + VSTREAM *bdat_get_stream; /* memory stream from BDAT chunk */ + VSTRING *bdat_get_buffer; /* read from memory stream */ + int bdat_prev_rec_type; } SMTPD_STATE; #define SMTPD_FLAG_HANGUP (1<<0) /* 421/521 disconnect */ @@ -198,7 +209,7 @@ typedef struct { /* Security: don't reset SMTPD_FLAG_AUTH_USED. */ #define SMTPD_MASK_MAIL_KEEP \ - ~(SMTPD_FLAG_SMTPUTF8) /* Fix 20140706 */ + ~(SMTPD_FLAG_SMTPUTF8) /* Fix 20140706 */ #define SMTPD_STATE_XFORWARD_INIT (1<<0) /* xforward preset done */ #define SMTPD_STATE_XFORWARD_NAME (1<<1) /* client name received */ @@ -223,7 +234,8 @@ extern void smtpd_state_reset(SMTPD_STATE *); */ #define SMTPD_AFTER_CONNECT "CONNECT" #define SMTPD_AFTER_DATA "DATA content" -#define SMTPD_AFTER_DOT "END-OF-MESSAGE" +#define SMTPD_AFTER_BDAT "BDAT content" +#define SMTPD_AFTER_EOM "END-OF-MESSAGE" /* * Other stages. These are sometimes used to change the way information is @@ -236,7 +248,8 @@ extern void smtpd_state_reset(SMTPD_STATE *); #define SMTPD_CMD_MAIL "MAIL" #define SMTPD_CMD_RCPT "RCPT" #define SMTPD_CMD_DATA "DATA" -#define SMTPD_CMD_EOD SMTPD_AFTER_DOT /* XXX Was: END-OF-DATA */ +#define SMTPD_CMD_BDAT "BDAT" +#define SMTPD_CMD_EOD SMTPD_AFTER_EOM /* XXX Was: END-OF-DATA */ #define SMTPD_CMD_RSET "RSET" #define SMTPD_CMD_NOOP "NOOP" #define SMTPD_CMD_VRFY "VRFY" @@ -321,6 +334,12 @@ extern void smtpd_state_reset(SMTPD_STATE *); */ #define SMTPD_IN_MAIL_TRANSACTION(state) ((state)->sender != 0) + /* + * Are we processing BDAT requests? + */ +#define SMTPD_PROCESSING_BDAT(state) \ + ((state)->bdat_state != SMTPD_BDAT_STAT_NONE) + /* * SMTPD peer information lookup. */ diff --git a/postfix/src/smtpd/smtpd_chat.c b/postfix/src/smtpd/smtpd_chat.c index bd7681331..295563e47 100644 --- a/postfix/src/smtpd/smtpd_chat.c +++ b/postfix/src/smtpd/smtpd_chat.c @@ -9,6 +9,10 @@ /* /* void smtpd_chat_pre_jail_init(void) /* +/* int smtpd_chat_query_limit(state, limit) +/* SMTPD_STATE *state; +/* int limit; +/* /* void smtpd_chat_query(state) /* SMTPD_STATE *state; /* @@ -27,6 +31,11 @@ /* /* smtpd_chat_pre_jail_init() performs one-time initialization. /* +/* smtpd_chat_query_limit() reads a line from the client that is +/* at most "limit" bytes long. A copy is appended to the SMTP +/* transaction log. The return value is non-zero for a complete +/* line or else zero if the length limit was exceeded. +/* /* smtpd_chat_query() receives a client request and appends a copy /* to the SMTP transaction log. /* @@ -106,7 +115,7 @@ static MAPS *smtpd_rej_ftr_maps; /* smtpd_chat_pre_jail_init - initialize */ -void smtpd_chat_pre_jail_init(void) +void smtpd_chat_pre_jail_init(void) { static int init_count = 0; @@ -151,7 +160,7 @@ static void smtp_chat_append(SMTPD_STATE *state, char *direction, /* smtpd_chat_query - receive and record an SMTP request */ -void smtpd_chat_query(SMTPD_STATE *state) +int smtpd_chat_query_limit(SMTPD_STATE *state, int limit) { int last_char; @@ -159,16 +168,17 @@ void smtpd_chat_query(SMTPD_STATE *state) * We can't parse or store input that exceeds var_line_limit, so we skip * over it to avoid loss of synchronization. */ - last_char = smtp_get(state->buffer, state->client, var_line_limit, + last_char = smtp_get(state->buffer, state->client, limit, SMTP_GET_FLAG_SKIP); smtp_chat_append(state, "In: ", STR(state->buffer)); if (last_char != '\n') msg_warn("%s: request longer than %d: %.30s...", - state->namaddr, var_line_limit, + state->namaddr, limit, printable(STR(state->buffer), '?')); if (msg_verbose) msg_info("< %s: %s", state->namaddr, STR(state->buffer)); + return (last_char == '\n'); } /* smtpd_chat_reply - format, send and record an SMTP response */ @@ -176,6 +186,16 @@ void smtpd_chat_query(SMTPD_STATE *state) void smtpd_chat_reply(SMTPD_STATE *state, const char *format,...) { va_list ap; + + va_start(ap, format); + vsmtpd_chat_reply(state, format, ap); + va_end(ap); +} + +/* vsmtpd_chat_reply - format, send and record an SMTP response */ + +void vsmtpd_chat_reply(SMTPD_STATE *state, const char *format, va_list ap) +{ int delay = 0; char *cp; char *next; @@ -189,9 +209,7 @@ void smtpd_chat_reply(SMTPD_STATE *state, const char *format,...) if (state->error_count >= var_smtpd_soft_erlim) sleep(delay = var_smtpd_err_sleep); - va_start(ap, format); vstring_vsprintf(state->buffer, format, ap); - va_end(ap); if ((*(cp = STR(state->buffer)) == '4' || *cp == '5') && ((smtpd_rej_ftr_maps != 0 diff --git a/postfix/src/smtpd/smtpd_chat.h b/postfix/src/smtpd/smtpd_chat.h index e64b1c92f..6b3a5472c 100644 --- a/postfix/src/smtpd/smtpd_chat.h +++ b/postfix/src/smtpd/smtpd_chat.h @@ -9,15 +9,25 @@ /* DESCRIPTION /* .nf + /* + * Global library. + */ +#include + /* * External interface. */ extern void smtpd_chat_pre_jail_init(void); extern void smtpd_chat_reset(SMTPD_STATE *); +extern int smtpd_chat_query_limit(SMTPD_STATE *, int); extern void smtpd_chat_query(SMTPD_STATE *); extern void PRINTFLIKE(2, 3) smtpd_chat_reply(SMTPD_STATE *, const char *,...); +extern void vsmtpd_chat_reply(SMTPD_STATE *, const char *, va_list); extern void smtpd_chat_notify(SMTPD_STATE *); +#define smtpd_chat_query(state) \ + ((void) smtpd_chat_query_limit((state), var_line_limit)) + /* LICENSE /* .ad /* .fi diff --git a/postfix/src/smtpd/smtpd_check.c b/postfix/src/smtpd/smtpd_check.c index 94e8c0181..f78d56cf7 100644 --- a/postfix/src/smtpd/smtpd_check.c +++ b/postfix/src/smtpd/smtpd_check.c @@ -2598,7 +2598,7 @@ static int check_table_result(SMTPD_STATE *state, const char *table, if (not_in_client_helo(state, table, "PREPEND", reply_class) == 0) return (SMTPD_CHECK_DUNNO); #endif - if (strcmp(state->where, SMTPD_AFTER_DOT) == 0) { + if (strcmp(state->where, SMTPD_AFTER_EOM) == 0) { msg_warn("access table %s: action PREPEND must be used before %s", table, VAR_EOD_CHECKS); return (SMTPD_CHECK_DUNNO); @@ -3910,7 +3910,9 @@ static int check_policy_service(SMTPD_STATE *state, const char *server, if (attr_clnt_request(policy_clnt->client, ATTR_FLAG_NONE, /* Query attributes. */ SEND_ATTR_STR(MAIL_ATTR_REQ, "smtpd_access_policy"), - SEND_ATTR_STR(MAIL_ATTR_PROTO_STATE, state->where), + SEND_ATTR_STR(MAIL_ATTR_PROTO_STATE, + STREQ(state->where, SMTPD_CMD_BDAT) ? + SMTPD_CMD_DATA : state->where), SEND_ATTR_STR(MAIL_ATTR_ACT_PROTO_NAME, state->protocol), SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, state->addr), SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_NAME, state->name), @@ -3929,7 +3931,8 @@ static int check_policy_service(SMTPD_STATE *state, const char *server, state->recipient ? state->recipient : ""), SEND_ATTR_INT(MAIL_ATTR_RCPT_COUNT, ((strcasecmp(state->where, SMTPD_CMD_DATA) == 0) || - (strcasecmp(state->where, SMTPD_AFTER_DOT) == 0)) ? + (strcasecmp(state->where, SMTPD_CMD_BDAT) == 0) || + (strcasecmp(state->where, SMTPD_AFTER_EOM) == 0)) ? state->rcpt_count : 0), SEND_ATTR_STR(MAIL_ATTR_QUEUEID, state->queue_id ? state->queue_id : ""), diff --git a/postfix/src/smtpd/smtpd_sasl_glue.c b/postfix/src/smtpd/smtpd_sasl_glue.c index 3a0782dc7..020c830fa 100644 --- a/postfix/src/smtpd/smtpd_sasl_glue.c +++ b/postfix/src/smtpd/smtpd_sasl_glue.c @@ -308,12 +308,11 @@ int smtpd_sasl_authenticate(SMTPD_STATE *state, /* * Receive the client response. "*" means that the client gives up. - * XXX For now we ignore the fact that an excessively long response - * will be chopped into multiple responses. To handle such responses, - * we need to change smtpd_chat_query() so that it returns an error - * indication. */ - smtpd_chat_query(state); + if (!smtpd_chat_query_limit(state, var_smtpd_sasl_resp_limit)) { + smtpd_chat_reply(state, "500 5.5.6 SASL response limit exceeded"); + return (-1); + } if (strcmp(STR(state->buffer), "*") == 0) { msg_warn("%s: SASL %s authentication aborted", state->namaddr, sasl_method); diff --git a/postfix/src/smtpd/smtpd_state.c b/postfix/src/smtpd/smtpd_state.c index 87e62fa44..f2f5f899c 100644 --- a/postfix/src/smtpd/smtpd_state.c +++ b/postfix/src/smtpd/smtpd_state.c @@ -150,7 +150,6 @@ void smtpd_state_init(SMTPD_STATE *state, VSTREAM *stream, state->tls_context = 0; #endif - /* * Minimal initialization to support external authentication (e.g., * XCLIENT) without having to enable SASL in main.cf. @@ -183,6 +182,13 @@ void smtpd_state_init(SMTPD_STATE *state, VSTREAM *stream, state->ehlo_argv = 0; state->ehlo_buf = 0; + + /* + * BDAT. + */ + state->bdat_state = SMTPD_BDAT_STAT_NONE; + state->bdat_get_stream = 0; + state->bdat_get_buffer = 0; } /* smtpd_state_reset - cleanup after disconnect */ @@ -231,4 +237,12 @@ void smtpd_state_reset(SMTPD_STATE *state) if (state->tlsproxy) /* still open after longjmp */ vstream_fclose(state->tlsproxy); #endif + + /* + * BDAT. + */ + if (state->bdat_get_stream) + (void) vstream_fclose(state->bdat_get_stream); + if (state->bdat_get_buffer) + vstring_free(state->bdat_get_buffer); } diff --git a/postfix/src/util/vbuf.c b/postfix/src/util/vbuf.c index 6f4ef4557..ecc6f197b 100644 --- a/postfix/src/util/vbuf.c +++ b/postfix/src/util/vbuf.c @@ -169,7 +169,8 @@ int vbuf_unget(VBUF *bp, int ch) int vbuf_get(VBUF *bp) { - return (bp->get_ready(bp) ? VBUF_EOF : VBUF_GET(bp)); + return (bp->get_ready(bp) ? + ((bp->flags |= VBUF_FLAG_EOF), VBUF_EOF) : VBUF_GET(bp)); } /* vbuf_put - handle write buffer full condition */ diff --git a/postfix/src/util/vstream.c b/postfix/src/util/vstream.c index b85d51159..674e79604 100644 --- a/postfix/src/util/vstream.c +++ b/postfix/src/util/vstream.c @@ -19,6 +19,11 @@ /* VSTRING *string; /* int flags; /* +/* VSTREAM *vstream_memreopen(stream, string, flags) +/* VSTREAM *stream; +/* VSTRING *string; +/* int flags; +/* /* int vstream_fclose(stream) /* VSTREAM *stream; /* @@ -205,6 +210,10 @@ /* will be in an indeterminate state while the stream is open, /* and will be null-terminated when the stream is closed. /* +/* vstream_memreopen() reopens a memory stream. When the +/* \fIstream\fR argument is a null pointer, the behavior is that +/* of vstream_memopen(). +/* /* vstream_fclose() closes the named buffered stream. The result /* is 0 in case of success, VSTREAM_EOF in case of problems. /* vstream_fclose() reports the same errors as vstream_ferror(). @@ -446,6 +455,8 @@ /* The deadline feature is activated. /* .IP VSTREAM_FLAG_DOUBLE /* The double-buffering feature is activated. +/* .IP VSTREAM_FLAG_MEMORY +/* The stream is connected to a VSTRING buffer. /* DIAGNOSTICS /* Panics: interface violations. Fatal errors: out of memory. /* SEE ALSO @@ -1692,11 +1703,12 @@ const char *vstream_peek_data(VSTREAM *vp) /* vstream_memopen - open a VSTRING */ -VSTREAM *vstream_memopen(VSTRING *string, int flags) +VSTREAM *vstream_memreopen(VSTREAM *stream, VSTRING *string, int flags) { - VSTREAM *stream; - - stream = vstream_subopen(); + if (stream == 0) + stream = vstream_subopen(); + else if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) == 0) + msg_panic("vstream_memreopen: cannot reopen non-memory stream"); stream->fd = -1; stream->read_fn = 0; stream->write_fn = 0; diff --git a/postfix/src/util/vstream.h b/postfix/src/util/vstream.h index ac6b02ca5..60197a77a 100644 --- a/postfix/src/util/vstream.h +++ b/postfix/src/util/vstream.h @@ -263,7 +263,9 @@ extern int vstream_tweak_tcp(VSTREAM *); /* * Read/write VSTRING memory. */ -VSTREAM *vstream_memopen(struct VSTRING *, int); +#define vstream_memopen(string, flags) \ + vstream_memreopen((VSTREAM *) 0, (string), (flags)) +VSTREAM *vstream_memreopen(VSTREAM *, struct VSTRING *, int); /* LICENSE /* .ad diff --git a/postfix/src/util/vstring_vstream.c b/postfix/src/util/vstring_vstream.c index bec981699..6c39140d5 100644 --- a/postfix/src/util/vstring_vstream.c +++ b/postfix/src/util/vstring_vstream.c @@ -6,6 +6,39 @@ /* SYNOPSIS /* #include /* +/* int vstring_get_flags(vp, fp, flags) +/* VSTRING *vp; +/* VSTREAM *fp; +/* int flags +/* +/* int vstring_get_flags_nonl(vp, fp, flags) +/* VSTRING *vp; +/* VSTREAM *fp; +/* int flags +/* +/* int vstring_get_flags_null(vp, fp, flags) +/* VSTRING *vp; +/* VSTREAM *fp; +/* int flags +/* +/* int vstring_get_flags_bound(vp, fp, flags, bound) +/* VSTRING *vp; +/* VSTREAM *fp; +/* ssize_t bound; +/* int flags +/* +/* int vstring_get_flags_nonl_bound(vp, fp, flags, bound) +/* VSTRING *vp; +/* VSTREAM *fp; +/* ssize_t bound; +/* int flags +/* +/* int vstring_get_flags_null_bound(vp, fp, flags, bound) +/* VSTRING *vp; +/* VSTREAM *fp; +/* ssize_t bound; +/* int flags +/* CONVENIENCE API /* int vstring_get(vp, fp) /* VSTRING *vp; /* VSTREAM *fp; @@ -36,19 +69,25 @@ /* The routines in this module each read one newline or null-terminated /* string from an input stream. In all cases the result is either the /* last character read, typically the record terminator, or VSTREAM_EOF. +/* The flags argument is VSTRING_GET_FLAG_NONE (default) or +/* VSTRING_GET_FLAG_APPEND (append instead of overwrite). /* -/* vstring_get() reads one line from the named stream, including the +/* vstring_get_flags() reads one line from the named stream, including the /* terminating newline character if present. /* -/* vstring_get_nonl() reads a line from the named stream and strips +/* vstring_get_flags_nonl() reads a line from the named stream and strips /* the trailing newline character. /* -/* vstring_get_null() reads a null-terminated string from the named +/* vstring_get_flags_null() reads a null-terminated string from the named /* stream. /* -/* the vstring_get_bound() routines read no more +/* the vstring_get_flags_bound() routines read no more /* than \fIbound\fR characters. Otherwise they behave like the /* unbounded versions documented above. +/* +/* The functions without _flags in their name accept the same +/* arguments except flags. These functions use the default +/* flags valie. /* DIAGNOSTICS /* Fatal errors: memory allocation failure. /* Panic: improper string bound. @@ -82,13 +121,14 @@ #define VSTRING_GET_RESULT(vp) \ (VSTRING_LEN(vp) > 0 ? vstring_end(vp)[-1] : VSTREAM_EOF) -/* vstring_get - read line from file, keep newline */ +/* vstring_get_flags - read line from file, keep newline */ -int vstring_get(VSTRING *vp, VSTREAM *fp) +int vstring_get_flags(VSTRING *vp, VSTREAM *fp, int flags) { int c; - VSTRING_RESET(vp); + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF) { VSTRING_ADDCH(vp, c); if (c == '\n') @@ -98,42 +138,46 @@ int vstring_get(VSTRING *vp, VSTREAM *fp) return (VSTRING_GET_RESULT(vp)); } -/* vstring_get_nonl - read line from file, strip newline */ +/* vstring_get_flags_nonl - read line from file, strip newline */ -int vstring_get_nonl(VSTRING *vp, VSTREAM *fp) +int vstring_get_flags_nonl(VSTRING *vp, VSTREAM *fp, int flags) { int c; - VSTRING_RESET(vp); + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n') VSTRING_ADDCH(vp, c); VSTRING_TERMINATE(vp); return (c == '\n' ? c : VSTRING_GET_RESULT(vp)); } -/* vstring_get_null - read null-terminated string from file */ +/* vstring_get_flags_null - read null-terminated string from file */ -int vstring_get_null(VSTRING *vp, VSTREAM *fp) +int vstring_get_flags_null(VSTRING *vp, VSTREAM *fp, int flags) { int c; - VSTRING_RESET(vp); + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0) VSTRING_ADDCH(vp, c); VSTRING_TERMINATE(vp); return (c == 0 ? c : VSTRING_GET_RESULT(vp)); } -/* vstring_get_bound - read line from file, keep newline, up to bound */ +/* vstring_get_flags_bound - read line from file, keep newline, up to bound */ -int vstring_get_bound(VSTRING *vp, VSTREAM *fp, ssize_t bound) +int vstring_get_flags_bound(VSTRING *vp, VSTREAM *fp, int flags, + ssize_t bound) { int c; if (bound <= 0) msg_panic("vstring_get_bound: invalid bound %ld", (long) bound); - VSTRING_RESET(vp); + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF) { VSTRING_ADDCH(vp, c); if (c == '\n') @@ -143,32 +187,36 @@ int vstring_get_bound(VSTRING *vp, VSTREAM *fp, ssize_t bound) return (VSTRING_GET_RESULT(vp)); } -/* vstring_get_nonl_bound - read line from file, strip newline, up to bound */ +/* vstring_get_flags_nonl_bound - read line from file, strip newline, up to bound */ -int vstring_get_nonl_bound(VSTRING *vp, VSTREAM *fp, ssize_t bound) +int vstring_get_flags_nonl_bound(VSTRING *vp, VSTREAM *fp, int flags, + ssize_t bound) { int c; if (bound <= 0) msg_panic("vstring_get_nonl_bound: invalid bound %ld", (long) bound); - VSTRING_RESET(vp); + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n') VSTRING_ADDCH(vp, c); VSTRING_TERMINATE(vp); return (c == '\n' ? c : VSTRING_GET_RESULT(vp)); } -/* vstring_get_null_bound - read null-terminated string from file */ +/* vstring_get_flags_null_bound - read null-terminated string from file */ -int vstring_get_null_bound(VSTRING *vp, VSTREAM *fp, ssize_t bound) +int vstring_get_flags_null_bound(VSTRING *vp, VSTREAM *fp, int flags, + ssize_t bound) { int c; if (bound <= 0) msg_panic("vstring_get_null_bound: invalid bound %ld", (long) bound); - VSTRING_RESET(vp); + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0) VSTRING_ADDCH(vp, c); VSTRING_TERMINATE(vp); diff --git a/postfix/src/util/vstring_vstream.h b/postfix/src/util/vstring_vstream.h index 4753929d1..e0dc801b4 100644 --- a/postfix/src/util/vstring_vstream.h +++ b/postfix/src/util/vstring_vstream.h @@ -19,12 +19,34 @@ /* * External interface. */ -extern int WARN_UNUSED_RESULT vstring_get(VSTRING *, VSTREAM *); -extern int WARN_UNUSED_RESULT vstring_get_nonl(VSTRING *, VSTREAM *); -extern int WARN_UNUSED_RESULT vstring_get_null(VSTRING *, VSTREAM *); -extern int WARN_UNUSED_RESULT vstring_get_bound(VSTRING *, VSTREAM *, ssize_t); -extern int WARN_UNUSED_RESULT vstring_get_nonl_bound(VSTRING *, VSTREAM *, ssize_t); -extern int WARN_UNUSED_RESULT vstring_get_null_bound(VSTRING *, VSTREAM *, ssize_t); +#define VSTRING_GET_FLAG_NONE (0) +#define VSTRING_GET_FLAG_APPEND (1<<1) /* append instead of overwrite */ + +extern int WARN_UNUSED_RESULT vstring_get_flags(VSTRING *, VSTREAM *, int); +extern int WARN_UNUSED_RESULT vstring_get_flags_nonl(VSTRING *, VSTREAM *, int); +extern int WARN_UNUSED_RESULT vstring_get_flags_null(VSTRING *, VSTREAM *, int); +extern int WARN_UNUSED_RESULT vstring_get_flags_bound(VSTRING *, VSTREAM *, int, ssize_t); +extern int WARN_UNUSED_RESULT vstring_get_flags_nonl_bound(VSTRING *, VSTREAM *, int, ssize_t); +extern int WARN_UNUSED_RESULT vstring_get_flags_null_bound(VSTRING *, VSTREAM *, int, ssize_t); + + /* + * Convenience aliases for most use cases. + */ +#define vstring_get(string, stream) \ + vstring_get_flags((string), (stream), VSTRING_GET_FLAG_NONE) +#define vstring_get_nonl(string, stream) \ + vstring_get_flags_nonl((string), (stream), VSTRING_GET_FLAG_NONE) +#define vstring_get_null(string, stream) \ + vstring_get_flags_null((string), (stream), VSTRING_GET_FLAG_NONE) + +#define vstring_get_bound(string, stream, size) \ + vstring_get_flags_bound((string), (stream), VSTRING_GET_FLAG_NONE, size) +#define vstring_get_nonl_bound(string, stream, size) \ + vstring_get_flags_nonl_bound((string), (stream), \ + VSTRING_GET_FLAG_NONE, size) +#define vstring_get_null_bound(string, stream, size) \ + vstring_get_flags_null_bound((string), (stream), \ + VSTRING_GET_FLAG_NONE, size) /* * Backwards compatibility for code that still uses the vstring_fgets()