-TNAME_CODE
-TNAME_MASK
-TNBBIO
+-TOPTIONS
-TPC_DBMS_INFO
-TPC_EVAL_CTX
-TPC_MASTER_ENT
-TSSL
-TSSL_CTX
-TSSL_SESSION
+-TSTATE
-TSTRING_LIST
-TSTRING_TABLE
-TSYS_EXITS_DETAIL
-TTLSMGR_SCACHE
-TTLSP_STATE
-TTLS_APPL_STATE
+-TTLS_CERTS
-TTLS_CLIENT_INIT_PROPS
-TTLS_CLIENT_START_PROPS
--TTLS_CERTS
-TTLS_DANE
-TTLS_PKEYS
-TTLS_PRNG_SEED_INFO
-Tsize_t
-Tssize_t
-Ttime_t
--TRESPONSE
--TSTATE
--TOPTIONS
Documentation: in smtpd.c, the comment that justifies the
454 reply for "TLS unavailable" cited the wrong RFC.
+20130404
+
+ Human factors: warning when a main.cf parameter has multiple
+ entries with different values. File: util/dict.c.
+
20130405
+ Feature: the recipient_delimiter parameter can now specify
+ a set of characters. A user name is now separated from its
+ address extension by the first character that matches the
+ recipient_delimiter set. Files: proto/postconf.proto,
+ src/global/mail_addr_find.c, src/global/mail_params.c,
+ src/global/split_addr.c, src/global/split_addr.h,
+ src/global/strip_addr.c, src/global/strip_addr.h,
+ src/global/strip_addr.ref, src/local/bounce_workaround.c,
+ src/local/local.c, src/local/local_expand.c, src/local/recipient.c,
+ src/local/resolve.c, src/oqmgr/qmgr_message.c, src/pipe/pipe.c,
+ src/qmgr/qmgr_message.c, src/smtpd/smtpd.c,
+ src/smtpd/smtpd_check.c, src/trivial-rewrite/transport.c,
+ src/trivial-rewrite/trivial-rewrite.c.
+
Feature: support for trust anchors, i.e. CA certificates
or public keys that will be used instead of conventional
root certificates, and revised fingerprint support. This
tls/tls_fprint.c, tls/tls_misc.c, tls/tls_verify.c,
util/argv.c, util/argv.h.
+20130409
+
+ Documentation: pointers to other actions under "ACCEPT
+ ACTIONS" and "REJECT ACTIONS". File: proto/access.
+
+20130410
+
+ Cleanup: more uniform permutation in dns_rr() by Victor
+ Duchovni & Son. File: dns/dns_rr.c.
+
+20130411
+
+ Documentation: clarified text about result formats. Files:
+ proto/canonical, proto/virtual.
+
20130414
Cleanup: the SMTP client connection management code now
posttls-finger/tlsmgrmem.c, posttls-finger/tlsmgrmem.h,
tls/tls.h, tls/tls_misc.c.
+20130423
+
+ Bugfix (introduced: Postfix 2.0): when myhostname is not
+ listed in mydestination, the trivial-rewrite resolver may
+ log "do not list <myhostname value> in both mydestination
+ and <name of non-mydestination domain list>". The fix is
+ to re-resolve a domain-less address after adding $myhostname
+ as the surrogate domain, so that it pops out with the right
+ address-class label. Problem reported by Quanah Gibson-Mount.
+ File: trivial-rewrite/resolve.c.
+
20130425
Non-production fixes: revert to using proxies (sender,
smtp/smtp.c, smtp/smtp.h, smtp/smtp_params.c,
smtp/smtp_tls_policy.c, tls/tls.h, tls/tls_level.c.
+20130512
+
+ Feature: allow an SMTP client to skip postscreen(8) tests
+ before or after the 220 greeting, based on its DNSBL score.
+ Suggested by Rob McGee (/dev/rob0). Files: mantools/postlink,
+ proto/postconf.proto, global/mail_params.h,
+ postscreen/postscreen.c, postscreen/postscreen.h,
+ postscreen/postscreen_early.c, postscreen/postscreen_state.c,
+ postscreen/postscreen_tests.c.
+
+20130513
+
+ Bugfix (introduced: 20130512): postscreen logged no "PASS
+ NEW" event when the pregreet tests were turned off and the
+ postscreen_dnsbl_whitelist_treshold feature was turned on.
+ Reported by Rob McGee (/dev/rob0). Files: postscreen/postscreen.h,
+ postscreen/postscreen_early.c.
+
+ Bugfix (introduced: 20130512): postscreen panic because the
+ logic for dnsbl result retrieval was changed. Reported by
+ Noel Jones. File: postscreen/postscreen_early.c.
+
+20130517
+
+ Cleanup: just like the postscreen DNS block test will use
+ partial scores when some DNS lookup result is unavailable,
+ the postscreen_dnsbl_whitelist_treshold feature will now
+ use partial scores instead of ignoring them. File:
+ postscreen/postscreen_early.c.
+
20130518
+ Bugfix (introduced: 1997): memory leak after error while
+ forwarding mail through the cleanup server. Viktor found
+ one, Wietse eliminated the rest. File: local/forward.c.
+
Feature: posttls-finger protocol and cipher grade selection
options. Leave protocol debug flags active across reconnects,
only suppress redundant logging of the certificate details.
Dukhovni. Files: proto/TLS_README.html, proto/postconf.proto,
src/global/mail_params.h, src/tls/tls.h, src/tls/tls_client.c,
src/tls/tls_misc.c.
-
read/write access conflicts and gives the new data to Postfix once that
data is available.
- * If you change a regexp: or pcre: file then Postfix may or may not pick up
- the file changes immediately. This is because a Postfix process reads the
- entire file into memory once and never examines the file again.
+ * If you change a regexp:, pcre:, cidr: or texthash: file then Postfix may
+ not pick up the file changes immediately. This is because a Postfix process
+ reads the entire file into memory once and never examines the file again.
o If the file is used by a short-running process such as smtpd(8),
cleanup(8) or local(8), there is no need to execute "postfix reload"
If you upgrade from Postfix 2.9 or earlier, read RELEASE_NOTES-2.10
before proceeding.
+Major changes with snapshot 20130512
+====================================
+
+Allow an SMTP client to skip postscreen(8) tests based on its
+postscreen_dnsbl_sites score.
+
+Specify a negative "postscreen_dnsbl_whitelist_threshold" to enable
+this feature. When a client passes the threshold value without
+having failed other tests, all pending or disabled tests are flagged
+as completed.
+
+Major changes with snapshot 20130405
+====================================
+
+The recipient_delimiter parameter can now specify a set of characters.
+A user name is now separated from its address extension by the first
+character that matches the recipient_delimiter set.
+
+For example, specify "recipient_delimiter = +-" to support both the
+Postfix-style "+" and the qmail-style "-" extension delimiter.
+
+As before, this implementation recognizes one delimiter character
+per email address, and one address extension per email address.
+
Major changes with snapshot 20130319
====================================
Things to do before the stable release:
+ Spell-check, double-word check, and HTML validator check.
+
Remove this file from the stable release.
Things to do after the stable release:
- Spellcheck and double-word check.
-
- Fix a false cache-sharing problem. After the SASL handshake,
- the connection cache client does not store SASL credentials
- in the destination properties; it stores them in the endpoint
- label only. When the connection cache client reuses the
- connection with smtp_reuse_nexthop(), it does not restore
- the SASL credentials. When it saves the connection afterwards,
- it creates a new endpoint label without SASL credentials,
- so the authenticated connection can now be used for unrelated
- deliveries.
+ Discourage the use of "after 220" tests in POSTSCREEN_README
+ and the documentation of individual parameter settings.
Begin code revision, after DANE support stabilizes. This
should be one pass that changes only names and no code.
- Run new source files through ccformat. If I do it now,
- almost every block of code or comments is changed. Having
- different formatting styles in the same project is PROBLEMATIC.
- There is a reason why ccformat is included with source code.
-
- Embed all statement-like macros in do { ... } while (0).
- This is especially necessary with macros that contain an
- "if" statement, or that contain multiple statements.
-
- Spell-check, double-word check, and HTML validator check.
-
- Use make(1) and cc(1) to convert the C++ like templates
- into debuggable source code, such that each statement has
- its own distinct line number (what a revolutionary idea).
-
All source code must specify its original author and
license statement. Some code modules specify Lutz Jaenicke
as the original author and fall under his liberal license.
Wietse as the original author, and Lutz Jaenicke's license,
which is wrong.
- Generally, macro and function names should make a program
- more clear, not merely reduce the number of programmer
- keystrokes; similar considerations hold for variable names
- and constants. Avoid 1-letter names except "for i=1 to
- some_bound". Instead of bare numbers use named constants
- in function argument lists.
-
Code clarity: replace obscure macro/function names: for
example SMTP_X(XXX) -> VAR_SMTP(XXX), as the purpose is to
choose between VAR_SMTP_XXX or VAR_LMTP_XXX; replace
contents of a buffer with the specified length). Replace r
with res_opt, ditto for other 1-letter names.
- Unnecessary complexity: the SMTP_SESSION "tls" field is
- mandatory (always allocated) therefore the content can be
- a permanent part of the SMTP_SESSION structure, just like
- SASL-related information. This avoids silly indirections
- all over the code, as well as awkward smtp_tls_sess_alloc()
- error semantics.
-
We have smtp_host_lookup, smtp_dns_resolver_options, and
now smtp_dns_support_level. Of these, smtp_dns_resolver_options
is orthogonal but the rest has overlap.
# mat is generated by address-based relay authoriza-
# tion schemes such as pop-before-smtp.
#
+# For other accept actions, see "OTHER ACTIONS" below.
+#
# REJECT ACTIONS
# Postfix version 2.3 and later support enhanced status
# codes as defined in RFC 3463. When no code is specified
#
# This feature is available in Postfix 2.1 and later.
#
+# For other reject actions, see "OTHER ACTIONS" below.
+#
# OTHER ACTIONS
# restriction...
# Apply the named UCE restriction(s) (permit, reject,
# TABLE FORMAT
# The input format for the postmap(1) command is as follows:
#
-# pattern result
+# pattern address
# When pattern matches a mail address, replace it by
-# the corresponding result.
+# the corresponding address.
#
# blank lines and comments
# Empty lines and whitespace-only lines are ignored,
# TABLE FORMAT
# The input format for the postmap(1) command is as follows:
#
-# pattern result
+# pattern address, address, ...
# When pattern matches a mail address, replace it by
-# the corresponding result.
+# the corresponding address.
#
# blank lines and comments
# Empty lines and whitespace-only lines are ignored,
or SQL server takes care of read/write access conflicts and gives
the new data to Postfix once that data is available. </p>
-<li> <p> If you change a <a href="regexp_table.5.html">regexp</a>: or <a href="pcre_table.5.html">pcre</a>: file then Postfix may or
+<li> <p> If you change a <a href="regexp_table.5.html">regexp</a>:, <a href="pcre_table.5.html">pcre</a>:, <a href="cidr_table.5.html">cidr</a>: or <a href="DATABASE_README.html#types">texthash</a>: file
+then Postfix
may not pick up the file changes immediately. This is because a
Postfix process reads the entire file into memory once and never
examines the file again. </p>
mat is generated by address-based relay authoriza-
tion schemes such as pop-before-smtp.
+ For other accept actions, see "OTHER ACTIONS" below.
+
<b>REJECT ACTIONS</b>
Postfix version 2.3 and later support enhanced status
codes as defined in <a href="http://tools.ietf.org/html/rfc3463">RFC 3463</a>. When no code is specified
This feature is available in Postfix 2.1 and later.
+ For other reject actions, see "OTHER ACTIONS" below.
+
<b>OTHER ACTIONS</b>
<i>restriction...</i>
Apply the named UCE restriction(s) (<b>permit</b>, <b>reject</b>,
<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> configuration parameter spec-
ifies a non-empty value. To get the behavior before Post-
fix 2.2, specify "<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> =
- static:all".
+ <a href="DATABASE_README.html#types">static</a>:all".
Typically, one would use the <a href="canonical.5.html"><b>canonical</b>(5)</a> table to replace
login names by <i>Firstname.Lastname</i>, or to clean up
<b>TABLE FORMAT</b>
The input format for the <a href="postmap.1.html"><b>postmap</b>(1)</a> command is as follows:
- <i>pattern result</i>
+ <i>pattern address</i>
When <i>pattern</i> matches a mail address, replace it by
- the corresponding <i>result</i>.
+ the corresponding <i>address</i>.
blank lines and comments
Empty lines and whitespace-only lines are ignored,
address is a sequence of three to eight hexadecimal
octet pairs separated by ":".
+ The <i>network</i><b>_</b><i>mask</i> is the number of high-order bits
+ in the <i>network</i><b>_</b><i>address</i> that the search string must
+ match.
+
Before comparisons are made, lookup keys and table
entries are converted from string to binary. There-
fore table entries will be matched regardless of
tory.
<b><a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> (empty)</b>
- The separator between user names and address exten-
- sions (user+foo).
+ The set of characters that can separate a user name
+ from its address extension (user+foo).
<b><a href="postconf.5.html#require_home_directory">require_home_directory</a> (no)</b>
Require that a <a href="local.8.html"><b>local</b>(8)</a> recipient's home directory
tory.
<b><a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> (empty)</b>
- The separator between user names and address exten-
- sions (user+foo).
+ The set of characters that can separate a user name
+ from its address extension (user+foo).
<b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
The syslog facility of Postfix logging.
<dt><b>$<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a></b></dt>
-<dd>The system-wide recipient address extension delimiter. </dd>
+<dd>The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the system-wide recipient
+address extension delimiter (Postfix 2.10 and earlier). </dd>
<dt><b>${name?value}</b></dt>
<dt><b>$<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a></b></dt>
-<dd>The system-wide recipient address extension delimiter. </dd>
+<dd>The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the system-wide recipient
+address extension delimiter (Postfix 2.10 and earlier). </dd>
<dt><b>${name?value}</b></dt>
<dt><b>$<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a></b></dt>
-<dd>The system-wide recipient address extension delimiter. </dd>
+<dd>The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the system-wide recipient
+address extension delimiter (Postfix 2.10 and earlier). </dd>
<dt><b>$shell</b></dt>
<p> This feature is available in Postfix 2.8. </p>
+</DD>
+
+<DT><b><a name="postscreen_dnsbl_whitelist_threshold">postscreen_dnsbl_whitelist_threshold</a>
+(default: 0)</b></DT><DD>
+
+<p> Allow a remote SMTP client to skip "before" and "after 220
+greeting" protocol tests, based on its combined DNSBL score as
+defined with the <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> parameter. </p>
+
+<p> Specify a negative value to enable this feature. When a client
+passes the <a href="postconf.5.html#postscreen_dnsbl_whitelist_threshold">postscreen_dnsbl_whitelist_threshold</a> without having
+failed other tests, all pending or disabled tests are flagged as
+completed with a time-to-live value equal to <a href="postconf.5.html#postscreen_dnsbl_ttl">postscreen_dnsbl_ttl</a>.
+When a test was already completed, its time-to-live value is updated
+if it was less than <a href="postconf.5.html#postscreen_dnsbl_ttl">postscreen_dnsbl_ttl</a>. </p>
+
+<p> This feature is available in Postfix 2.11. </p>
+
+
</DD>
<DT><b><a name="postscreen_enforce_tls">postscreen_enforce_tls</a>
<DT><b><a name="recipient_delimiter">recipient_delimiter</a>
(default: empty)</b></DT><DD>
-<p>
-The separator between user names and address extensions (user+foo).
-See <a href="canonical.5.html">canonical(5)</a>, <a href="local.8.html">local(8)</a>, <a href="relocated.5.html">relocated(5)</a> and <a href="virtual.5.html">virtual(5)</a> for the
-effects this has on aliases, canonical, virtual, relocated and
-on .forward file lookups. Basically, the software tries user+foo
-and .forward+foo before trying user and .forward.
+<p> The set of characters that can separate a user name from its
+address extension (user+foo). See <a href="canonical.5.html">canonical(5)</a>, <a href="local.8.html">local(8)</a>, <a href="relocated.5.html">relocated(5)</a>
+and <a href="virtual.5.html">virtual(5)</a> for the effects this has on aliases, canonical,
+virtual, and relocated lookups. Basically, the software tries
+user+foo and .forward+foo before trying user and .forward. </p>
+
+<p> This implementation recognizes one delimiter character per email
+address, and one address extension per email address. </p>
+
+<p> When the <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> set contains multiple characters
+(Postfix 2.11 and later), a user name is separated from its address
+extension by the first character that matches the <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a>
+set. </p>
+
+<p> When used in <a href="postconf.5.html#forward_path">forward_path</a>, ${<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a>} is replaced
+with the recipient delimiter that was found in the recipient email
+address (Postfix 2.11 and later), or it is replaced with the <a href="postconf.5.html">main.cf</a>
+<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> parameter value (Postfix 2.10 and earlier).
</p>
+<p> The <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> is not applied to the mailer-daemon
+address, the postmaster address, or the double-bounce address. With
+the default "<a href="postconf.5.html#owner_request_special">owner_request_special</a> = yes" setting, the <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a>
+is also not applied to addresses with the special "owner-" prefix
+or the special "-request" suffix. </p>
+
<p>
-Example:
+Examples:
</p>
<pre>
+# Handle Postfix-style extensions.
<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> = +
</pre>
+<pre>
+# Handle both Postfix and qmail extensions (Postfix 2.11 and later).
+recipient_delimiters = +-
+</pre>
+
+<pre>
+# Use .forward for mail without address extension, and for mail with
+# an unrecognized address extension.
+<a href="postconf.5.html#forward_path">forward_path</a> = $home/.forward${<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a>}${extension},
+ $home/.forward
+</pre>
+
</DD>
The internal service that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> hands off
allowed connections to.
+ Available in Postfix version 2.11 and later:
+
+ <b><a href="postconf.5.html#postscreen_dnsbl_whitelist_threshold">postscreen_dnsbl_whitelist_threshold</a> (0)</b>
+ Allow a remote SMTP client to skip "before" and
+ "after 220 greeting" protocol tests, based on its
+ combined DNSBL score as defined with the
+ <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> parameter.
+
<b>AFTER-GREETING TESTS</b>
- These tests are executed after the remote SMTP client
+ These tests are executed after the remote SMTP client
receives the "220 servername" greeting. If a client passes
- all tests during this phase, it will receive a 4XX
- response to RCPT TO commands until the client hangs up.
+ all tests during this phase, it will receive a 4XX
+ response to RCPT TO commands until the client hangs up.
After this, the client will be allowed to talk directly to
a Postfix SMTP server process.
<b><a href="postconf.5.html#postscreen_bare_newline_action">postscreen_bare_newline_action</a> (ignore)</b>
- The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when a remote
- SMTP client sends a bare newline character, that
+ The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when a remote
+ SMTP client sends a bare newline character, that
is, a newline not preceded by carriage return.
<b><a href="postconf.5.html#postscreen_bare_newline_enable">postscreen_bare_newline_enable</a> (no)</b>
- Enable "bare newline" SMTP protocol tests in the
+ Enable "bare newline" SMTP protocol tests in the
<a href="postscreen.8.html"><b>postscreen</b>(8)</a> server.
<b><a href="postconf.5.html#postscreen_disable_vrfy_command">postscreen_disable_vrfy_command</a> ($<a href="postconf.5.html#disable_vrfy_command">disable_vrfy_command</a>)</b>
- Disable the SMTP VRFY command in the <a href="postscreen.8.html"><b>postscreen</b>(8)</a>
+ Disable the SMTP VRFY command in the <a href="postscreen.8.html"><b>postscreen</b>(8)</a>
daemon.
<b><a href="postconf.5.html#postscreen_forbidden_commands">postscreen_forbidden_commands</a> ($<a href="postconf.5.html#smtpd_forbidden_commands">smtpd_forbidden_commands</a>)</b>
siders in violation of the SMTP protocol.
<b><a href="postconf.5.html#postscreen_helo_required">postscreen_helo_required</a> ($<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a>)</b>
- Require that a remote SMTP client sends HELO or
+ Require that a remote SMTP client sends HELO or
EHLO before commencing a MAIL transaction.
<b><a href="postconf.5.html#postscreen_non_smtp_command_action">postscreen_non_smtp_command_action</a> (drop)</b>
- The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when a remote
- SMTP client sends non-SMTP commands as specified
+ The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when a remote
+ SMTP client sends non-SMTP commands as specified
with the <a href="postconf.5.html#postscreen_forbidden_commands">postscreen_forbidden_commands</a> parameter.
<b><a href="postconf.5.html#postscreen_non_smtp_command_enable">postscreen_non_smtp_command_enable</a> (no)</b>
- Enable "non-SMTP command" tests in the
+ Enable "non-SMTP command" tests in the
<a href="postscreen.8.html"><b>postscreen</b>(8)</a> server.
<b><a href="postconf.5.html#postscreen_pipelining_action">postscreen_pipelining_action</a> (enforce)</b>
- The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when a remote
- SMTP client sends multiple commands instead of
- sending one command and waiting for the server to
+ The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when a remote
+ SMTP client sends multiple commands instead of
+ sending one command and waiting for the server to
respond.
<b><a href="postconf.5.html#postscreen_pipelining_enable">postscreen_pipelining_enable</a> (no)</b>
- Enable "pipelining" SMTP protocol tests in the
+ Enable "pipelining" SMTP protocol tests in the
<a href="postscreen.8.html"><b>postscreen</b>(8)</a> server.
<b>CACHE CONTROLS</b>
<b><a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a> (12h)</b>
- The amount of time between <a href="postscreen.8.html"><b>postscreen</b>(8)</a> cache
+ The amount of time between <a href="postscreen.8.html"><b>postscreen</b>(8)</a> cache
cleanup runs.
<b><a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a> (btree:$data_direc-</b>
<b>tory/postscreen_cache)</b>
- Persistent storage for the <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server
+ Persistent storage for the <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server
decisions.
<b><a href="postconf.5.html#postscreen_cache_retention_time">postscreen_cache_retention_time</a> (7d)</b>
The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will cache an
- expired temporary whitelist entry before it is
+ expired temporary whitelist entry before it is
removed.
<b><a href="postconf.5.html#postscreen_bare_newline_ttl">postscreen_bare_newline_ttl</a> (30d)</b>
- The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the
+ The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the
result from a successful "bare newline" SMTP proto-
col test.
<b><a href="postconf.5.html#postscreen_dnsbl_ttl">postscreen_dnsbl_ttl</a> (1h)</b>
- The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the
+ The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the
result from a successful DNS blocklist test.
<b><a href="postconf.5.html#postscreen_greet_ttl">postscreen_greet_ttl</a> (1d)</b>
- The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the
+ The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the
result from a successful PREGREET test.
<b><a href="postconf.5.html#postscreen_non_smtp_command_ttl">postscreen_non_smtp_command_ttl</a> (30d)</b>
- The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the
- result from a successful "non_smtp_command" SMTP
+ The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the
+ result from a successful "non_smtp_command" SMTP
protocol test.
<b><a href="postconf.5.html#postscreen_pipelining_ttl">postscreen_pipelining_ttl</a> (30d)</b>
- The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the
+ The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the
result from a successful "pipelining" SMTP protocol
test.
<b>RESOURCE CONTROLS</b>
<b><a href="postconf.5.html#line_length_limit">line_length_limit</a> (2048)</b>
- Upon input, long lines are chopped up into pieces
- of at most this length; upon delivery, long lines
+ Upon input, long lines are chopped up into pieces
+ of at most this length; upon delivery, long lines
are reconstructed.
<b><a href="postconf.5.html#postscreen_client_connection_count_limit">postscreen_client_connection_count_limit</a></b>
<b>($<a href="postconf.5.html#smtpd_client_connection_count_limit">smtpd_client_connection_count_limit</a>)</b>
- How many simultaneous connections any remote SMTP
- client is allowed to have with the <a href="postscreen.8.html"><b>postscreen</b>(8)</a>
+ How many simultaneous connections any remote SMTP
+ client is allowed to have with the <a href="postscreen.8.html"><b>postscreen</b>(8)</a>
daemon.
<b><a href="postconf.5.html#postscreen_command_count_limit">postscreen_command_count_limit</a> (20)</b>
- The limit on the total number of commands per SMTP
- session for <a href="postscreen.8.html"><b>postscreen</b>(8)</a>'s built-in SMTP protocol
+ The limit on the total number of commands per SMTP
+ session for <a href="postscreen.8.html"><b>postscreen</b>(8)</a>'s built-in SMTP protocol
engine.
<b><a href="postconf.5.html#postscreen_command_time_limit">postscreen_command_time_limit</a> (${stress?10}${stress:300}s)</b>
- The time limit to read an entire command line with
+ The time limit to read an entire command line with
<a href="postscreen.8.html"><b>postscreen</b>(8)</a>'s built-in SMTP protocol engine.
<b><a href="postconf.5.html#postscreen_post_queue_limit">postscreen_post_queue_limit</a> ($<a href="postconf.5.html#default_process_limit">default_process_limit</a>)</b>
- The number of clients that can be waiting for ser-
+ The number of clients that can be waiting for ser-
vice from a real Postfix SMTP server process.
<b><a href="postconf.5.html#postscreen_pre_queue_limit">postscreen_pre_queue_limit</a> ($<a href="postconf.5.html#default_process_limit">default_process_limit</a>)</b>
- The number of non-whitelisted clients that can be
- waiting for a decision whether they will receive
+ The number of non-whitelisted clients that can be
+ waiting for a decision whether they will receive
service from a real Postfix SMTP server process.
<b><a href="postconf.5.html#postscreen_watchdog_timeout">postscreen_watchdog_timeout</a> (10s)</b>
- How much time a <a href="postscreen.8.html"><b>postscreen</b>(8)</a> process may take to
- respond to a remote SMTP client command or to per-
+ How much time a <a href="postscreen.8.html"><b>postscreen</b>(8)</a> process may take to
+ respond to a remote SMTP client command or to per-
form a cache operation before it is terminated by a
built-in watchdog timer.
<b>STARTTLS CONTROLS</b>
<b><a href="postconf.5.html#postscreen_tls_security_level">postscreen_tls_security_level</a> ($<a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a>)</b>
- The SMTP TLS security level for the <a href="postscreen.8.html"><b>postscreen</b>(8)</a>
- server; when a non-empty value is specified, this
+ The SMTP TLS security level for the <a href="postscreen.8.html"><b>postscreen</b>(8)</a>
+ server; when a non-empty value is specified, this
overrides the obsolete parameters
<a href="postconf.5.html#postscreen_use_tls">postscreen_use_tls</a> and <a href="postconf.5.html#postscreen_enforce_tls">postscreen_enforce_tls</a>.
<b><a href="postconf.5.html#tlsproxy_service_name">tlsproxy_service_name</a> (tlsproxy)</b>
- The name of the <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> service entry in mas-
+ The name of the <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> service entry in mas-
ter.cf.
<b>OBSOLETE STARTTLS SUPPORT CONTROLS</b>
- These parameters are supported for compatibility with
+ These parameters are supported for compatibility with
<a href="smtpd.8.html"><b>smtpd</b>(8)</a> legacy parameters.
<b><a href="postconf.5.html#postscreen_use_tls">postscreen_use_tls</a> ($<a href="postconf.5.html#smtpd_use_tls">smtpd_use_tls</a>)</b>
- Opportunistic TLS: announce STARTTLS support to
+ Opportunistic TLS: announce STARTTLS support to
remote SMTP clients, but do not require that
clients use TLS encryption.
<b><a href="postconf.5.html#postscreen_enforce_tls">postscreen_enforce_tls</a> ($<a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a>)</b>
- Mandatory TLS: announce STARTTLS support to remote
- SMTP clients, and require that clients use TLS
+ Mandatory TLS: announce STARTTLS support to remote
+ SMTP clients, and require that clients use TLS
encryption.
<b>MISCELLANEOUS CONTROLS</b>
<b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
- The default location of the Postfix <a href="postconf.5.html">main.cf</a> and
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and
<a href="master.5.html">master.cf</a> configuration files.
<b><a href="postconf.5.html#delay_logging_resolution_limit">delay_logging_resolution_limit</a> (2)</b>
- The maximal number of digits after the decimal
+ The maximal number of digits after the decimal
point when logging sub-second delay values.
<b><a href="postconf.5.html#command_directory">command_directory</a> (see 'postconf -d' output)</b>
- The location of all postfix administrative com-
+ The location of all postfix administrative com-
mands.
<b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
- The maximum amount of time that an idle Postfix
- daemon process waits for an incoming connection
+ The maximum amount of time that an idle Postfix
+ daemon process waits for an incoming connection
before terminating voluntarily.
<b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
- The process ID of a Postfix command or daemon
+ The process ID of a Postfix command or daemon
process.
<b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
- The process name of a Postfix command or daemon
+ The process name of a Postfix command or daemon
process.
<b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
The syslog facility of Postfix logging.
<b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
- The mail system name that is prepended to the
- process name in syslog records, so that "smtpd"
+ The mail system name that is prepended to the
+ process name in syslog records, so that "smtpd"
becomes, for example, "postfix/smtpd".
<b>SEE ALSO</b>
<a href="POSTSCREEN_README.html">POSTSCREEN_README</a>, Postfix Postscreen Howto
<b>LICENSE</b>
- The Secure Mailer license must be distributed with this
+ The Secure Mailer license must be distributed with this
software.
<b>HISTORY</b>
This service was introduced with Postfix version 2.8.
- Many ideas in <a href="postscreen.8.html"><b>postscreen</b>(8)</a> were explored in earlier work
- by Michael Tokarev, in OpenBSD spamd, and in MailChannels
+ Many ideas in <a href="postscreen.8.html"><b>postscreen</b>(8)</a> were explored in earlier work
+ by Michael Tokarev, in OpenBSD spamd, and in MailChannels
Traffic Control.
<b>AUTHOR(S)</b>
Available in Postfix version 2.1 and later:
- <b><a href="postconf.5.html#resolve_null_domain">resolve_null_domain</a> (no)</b>
- Resolve an address that ends in the "@" null domain
- as if the local hostname were specified, instead of
- rejecting the address as invalid.
-
<b><a href="postconf.5.html#smtpd_reject_unlisted_sender">smtpd_reject_unlisted_sender</a> (no)</b>
Request that the Postfix SMTP server rejects mail
from unknown sender addresses, even when no
tory.
<b><a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> (empty)</b>
- The separator between user names and address exten-
- sions (user+foo).
+ The set of characters that can separate a user name
+ from its address extension (user+foo).
<b><a href="postconf.5.html#smtpd_banner">smtpd_banner</a> ($<a href="postconf.5.html#myhostname">myhostname</a> ESMTP $<a href="postconf.5.html#mail_name">mail_name</a>)</b>
The text that follows the 220 status code in the
Resolve a recipient address safely instead of cor-
rectly, by looking inside quotes.
+ Available with Postfix version 2.1 and later:
+
<b><a href="postconf.5.html#resolve_null_domain">resolve_null_domain</a> (no)</b>
Resolve an address that ends in the "@" null domain
as if the local hostname were specified, instead of
rejecting the address as invalid.
+ Available with Postfix version 2.3 and later:
+
<b><a href="postconf.5.html#resolve_numeric_domain">resolve_numeric_domain</a> (no)</b>
Resolve "user@ipaddress" as "user@[ipaddress]",
instead of rejecting the address as invalid.
information.
<b><a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> (empty)</b>
- The separator between user names and address exten-
- sions (user+foo).
+ The set of characters that can separate a user name
+ from its address extension (user+foo).
<b><a href="postconf.5.html#swap_bangpath">swap_bangpath</a> (yes)</b>
Enable the rewriting of "site!user" into
<b>TABLE FORMAT</b>
The input format for the <a href="postmap.1.html"><b>postmap</b>(1)</a> command is as follows:
- <i>pattern result</i>
+ <i>pattern address, address, ...</i>
When <i>pattern</i> matches a mail address, replace it by
- the corresponding <i>result</i>.
+ the corresponding <i>address</i>.
blank lines and comments
Empty lines and whitespace-only lines are ignored,
An all-numerical result is treated as OK. This format is
generated by address-based relay authorization schemes
such as pop-before-smtp.
+.PP
+For other accept actions, see "OTHER ACTIONS" below.
.SH "REJECT ACTIONS"
.na
.nf
Prior to Postfix 2.6, the SMTP reply code is 450.
.sp
This feature is available in Postfix 2.1 and later.
+.PP
+For other reject actions, see "OTHER ACTIONS" below.
.SH "OTHER ACTIONS"
.na
.nf
.ad
.fi
The input format for the \fBpostmap\fR(1) command is as follows:
-.IP "\fIpattern result\fR"
+.IP "\fIpattern address\fR"
When \fIpattern\fR matches a mail address, replace it by the
-corresponding \fIresult\fR.
+corresponding \fIaddress\fR.
.IP "blank lines and comments"
Empty lines and whitespace-only lines are ignored, as
are lines whose first non-whitespace character is a `#'.
separated by ".", and an IPv6 network address is a sequence
of three to eight hexadecimal octet pairs separated by ":".
+The \fInetwork_mask\fR is the number of high-order bits in
+the \fInetwork_address\fR that the search string must match.
+
Before comparisons are made, lookup keys and table entries
are converted from string to binary. Therefore table entries
will be matched regardless of redundant zero characters.
The entire recipient localpart.
.br
.IP "\fB$recipient_delimiter\fR"
-The system-wide recipient address extension delimiter.
+The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the system-wide recipient
+address extension delimiter (Postfix 2.10 and earlier).
.br
.IP "\fB${name?value}\fR"
Expands to \fIvalue\fR when \fI$name\fR is non-empty.
The entire recipient localpart.
.br
.IP "\fB$recipient_delimiter\fR"
-The system-wide recipient address extension delimiter.
+The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the system-wide recipient
+address extension delimiter (Postfix 2.10 and earlier).
.br
.IP "\fB${name?value}\fR"
Expands to \fIvalue\fR when \fI$name\fR is non-empty.
The full recipient address.
.br
.IP "\fB$recipient_delimiter\fR"
-The system-wide recipient address extension delimiter.
+The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the system-wide recipient
+address extension delimiter (Postfix 2.10 and earlier).
.br
.IP "\fB$shell\fR"
The recipient's login shell.
(seconds), m (minutes), h (hours), d (days), w (weeks).
.PP
This feature is available in Postfix 2.8.
+.SH postscreen_dnsbl_whitelist_threshold (default: 0)
+Allow a remote SMTP client to skip "before" and "after 220
+greeting" protocol tests, based on its combined DNSBL score as
+defined with the postscreen_dnsbl_sites parameter.
+.PP
+Specify a negative value to enable this feature. When a client
+passes the postscreen_dnsbl_whitelist_threshold without having
+failed other tests, all pending or disabled tests are flagged as
+completed with a time-to-live value equal to postscreen_dnsbl_ttl.
+When a test was already completed, its time-to-live value is updated
+if it was less than postscreen_dnsbl_ttl.
+.PP
+This feature is available in Postfix 2.11.
.SH postscreen_enforce_tls (default: $smtpd_enforce_tls)
Mandatory TLS: announce STARTTLS support to remote SMTP clients, and
require that clients use TLS encryption. See smtpd_postscreen_enforce_tls
.ad
.ft R
.SH recipient_delimiter (default: empty)
-The separator between user names and address extensions (user+foo).
-See \fBcanonical\fR(5), \fBlocal\fR(8), \fBrelocated\fR(5) and \fBvirtual\fR(5) for the
-effects this has on aliases, canonical, virtual, relocated and
-on .forward file lookups. Basically, the software tries user+foo
-and .forward+foo before trying user and .forward.
+The set of characters that can separate a user name from its
+address extension (user+foo). See \fBcanonical\fR(5), \fBlocal\fR(8), \fBrelocated\fR(5)
+and \fBvirtual\fR(5) for the effects this has on aliases, canonical,
+virtual, and relocated lookups. Basically, the software tries
+user+foo and .forward+foo before trying user and .forward.
+.PP
+This implementation recognizes one delimiter character per email
+address, and one address extension per email address.
+.PP
+When the recipient_delimiter set contains multiple characters
+(Postfix 2.11 and later), a user name is separated from its address
+extension by the first character that matches the recipient_delimiter
+set.
+.PP
+When used in forward_path, ${recipient_delimiter} is replaced
+with the recipient delimiter that was found in the recipient email
+address (Postfix 2.11 and later), or it is replaced with the main.cf
+recipient_delimiter parameter value (Postfix 2.10 and earlier).
+.PP
+The recipient_delimiter is not applied to the mailer-daemon
+address, the postmaster address, or the double-bounce address. With
+the default "owner_request_special = yes" setting, the recipient_delimiter
+is also not applied to addresses with the special "owner-" prefix
+or the special "-request" suffix.
.PP
-Example:
+Examples:
.PP
.nf
.na
.ft C
+# Handle Postfix-style extensions.
recipient_delimiter = +
.fi
.ad
.ft R
+.PP
+.nf
+.na
+.ft C
+# Handle both Postfix and qmail extensions (Postfix 2.11 and later).
+recipient_delimiters = +-
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+# Use .forward for mail without address extension, and for mail with
+# an unrecognized address extension.
+forward_path = $home/.forward${recipient_delimiter}${extension},
+ $home/.forward
+.fi
+.ad
+.ft R
.SH reject_code (default: 554)
The numerical Postfix SMTP server response code when a remote SMTP
client request is rejected by the "reject" restriction.
.ad
.fi
The input format for the \fBpostmap\fR(1) command is as follows:
-.IP "\fIpattern result\fR"
+.IP "\fIpattern address, address, ...\fR"
When \fIpattern\fR matches a mail address, replace it by the
-corresponding \fIresult\fR.
+corresponding \fIaddress\fR.
.IP "blank lines and comments"
Empty lines and whitespace-only lines are ignored, as
are lines whose first non-whitespace character is a `#'.
.IP "\fBqueue_directory (see 'postconf -d' output)\fR"
The location of the Postfix top-level queue directory.
.IP "\fBrecipient_delimiter (empty)\fR"
-The separator between user names and address extensions (user+foo).
+The set of characters that can separate a user name from its
+address extension (user+foo).
.IP "\fBrequire_home_directory (no)\fR"
Require that a \fBlocal\fR(8) recipient's home directory exists
before mail delivery is attempted.
.IP "\fBqueue_directory (see 'postconf -d' output)\fR"
The location of the Postfix top-level queue directory.
.IP "\fBrecipient_delimiter (empty)\fR"
-The separator between user names and address extensions (user+foo).
+The set of characters that can separate a user name from its
+address extension (user+foo).
.IP "\fBsyslog_facility (mail)\fR"
The syslog facility of Postfix logging.
.IP "\fBsyslog_name (see 'postconf -d' output)\fR"
.IP "\fBsmtpd_service_name (smtpd)\fR"
The internal service that \fBpostscreen\fR(8) hands off allowed
connections to.
+.PP
+Available in Postfix version 2.11 and later:
+.IP "\fBpostscreen_dnsbl_whitelist_threshold (0)\fR"
+Allow a remote SMTP client to skip "before" and "after 220
+greeting" protocol tests, based on its combined DNSBL score as
+defined with the postscreen_dnsbl_sites parameter.
.SH "AFTER-GREETING TESTS"
.na
.nf
not contain RFC 822 style comments or phrases.
.PP
Available in Postfix version 2.1 and later:
-.IP "\fBresolve_null_domain (no)\fR"
-Resolve an address that ends in the "@" null domain as if the
-local hostname were specified, instead of rejecting the address as
-invalid.
.IP "\fBsmtpd_reject_unlisted_sender (no)\fR"
Request that the Postfix SMTP server rejects mail from unknown
sender addresses, even when no explicit reject_unlisted_sender
.IP "\fBqueue_directory (see 'postconf -d' output)\fR"
The location of the Postfix top-level queue directory.
.IP "\fBrecipient_delimiter (empty)\fR"
-The separator between user names and address extensions (user+foo).
+The set of characters that can separate a user name from its
+address extension (user+foo).
.IP "\fBsmtpd_banner ($myhostname ESMTP $mail_name)\fR"
The text that follows the 220 status code in the SMTP greeting
banner.
.IP "\fBresolve_dequoted_address (yes)\fR"
Resolve a recipient address safely instead of correctly, by
looking inside quotes.
+.PP
+Available with Postfix version 2.1 and later:
.IP "\fBresolve_null_domain (no)\fR"
Resolve an address that ends in the "@" null domain as if the
local hostname were specified, instead of rejecting the address as
invalid.
+.PP
+Available with Postfix version 2.3 and later:
.IP "\fBresolve_numeric_domain (no)\fR"
Resolve "user@ipaddress" as "user@[ipaddress]", instead of
rejecting the address as invalid.
With locally submitted mail, append the string ".$mydomain" to
addresses that have no ".domain" information.
.IP "\fBrecipient_delimiter (empty)\fR"
-The separator between user names and address extensions (user+foo).
+The set of characters that can separate a user name from its
+address extension (user+foo).
.IP "\fBswap_bangpath (yes)\fR"
Enable the rewriting of "site!user" into "user@site".
.PP
s;\bpostscreen_dnsbl_reply_map\b;<a href="postconf.5.html#postscreen_dnsbl_reply_map">$&</a>;g;
s;\bpostscreen_dnsbl_sites\b;<a href="postconf.5.html#postscreen_dnsbl_sites">$&</a>;g;
s;\bpostscreen_dnsbl_thresh[-</bB>]*\n* *[<bB>]*old\b;<a href="postconf.5.html#postscreen_dnsbl_threshold">$&</a>;g;
+ s;\bpostscreen_dnsbl_whitelist_thresh[-</bB>]*\n* *[<bB>]*old\b;<a href="postconf.5.html#postscreen_dnsbl_whitelist_threshold">$&</a>;g;
s;\bpostscreen_dnsbl_action\b;<a href="postconf.5.html#postscreen_dnsbl_action">$&</a>;g;
s;\bpostscreen_dnsbl_ttl\b;<a href="postconf.5.html#postscreen_dnsbl_ttl">$&</a>;g;
s;\bpostscreen_for[-</bB>]*\n*[ <bB>]*bidden_commands\b;<a href="postconf.5.html#postscreen_forbidden_commands">$&</a>;g;
or SQL server takes care of read/write access conflicts and gives
the new data to Postfix once that data is available. </p>
-<li> <p> If you change a regexp: or pcre: file then Postfix may or
+<li> <p> If you change a regexp:, pcre:, cidr: or texthash: file
+then Postfix
may not pick up the file changes immediately. This is because a
Postfix process reads the entire file into memory once and never
examines the file again. </p>
# An all-numerical result is treated as OK. This format is
# generated by address-based relay authorization schemes
# such as pop-before-smtp.
+# .PP
+# For other accept actions, see "OTHER ACTIONS" below.
# REJECT ACTIONS
# .ad
# .fi
# Prior to Postfix 2.6, the SMTP reply code is 450.
# .sp
# This feature is available in Postfix 2.1 and later.
+# .PP
+# For other reject actions, see "OTHER ACTIONS" below.
# OTHER ACTIONS
# .ad
# .fi
# .ad
# .fi
# The input format for the \fBpostmap\fR(1) command is as follows:
-# .IP "\fIpattern result\fR"
+# .IP "\fIpattern address\fR"
# When \fIpattern\fR matches a mail address, replace it by the
-# corresponding \fIresult\fR.
+# corresponding \fIaddress\fR.
# .IP "blank lines and comments"
# Empty lines and whitespace-only lines are ignored, as
# are lines whose first non-whitespace character is a `#'.
# separated by ".", and an IPv6 network address is a sequence
# of three to eight hexadecimal octet pairs separated by ":".
#
+# The \fInetwork_mask\fR is the number of high-order bits in
+# the \fInetwork_address\fR that the search string must match.
+#
# Before comparisons are made, lookup keys and table entries
# are converted from string to binary. Therefore table entries
# will be matched regardless of redundant zero characters.
<dt><b>$recipient_delimiter</b></dt>
-<dd>The system-wide recipient address extension delimiter. </dd>
+<dd>The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the system-wide recipient
+address extension delimiter (Postfix 2.10 and earlier). </dd>
<dt><b>${name?value}</b></dt>
<dt><b>$recipient_delimiter</b></dt>
-<dd>The system-wide recipient address extension delimiter. </dd>
+<dd>The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the system-wide recipient
+address extension delimiter (Postfix 2.10 and earlier). </dd>
<dt><b>${name?value}</b></dt>
<dt><b>$recipient_delimiter</b></dt>
-<dd>The system-wide recipient address extension delimiter. </dd>
+<dd>The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the system-wide recipient
+address extension delimiter (Postfix 2.10 and earlier). </dd>
<dt><b>$shell</b></dt>
recipient_canonical_maps = hash:/etc/postfix/recipient_canonical
</pre>
-%PARAM recipient_delimiter
+%PARAM recipient_delimiter
-<p>
-The separator between user names and address extensions (user+foo).
-See canonical(5), local(8), relocated(5) and virtual(5) for the
-effects this has on aliases, canonical, virtual, relocated and
-on .forward file lookups. Basically, the software tries user+foo
-and .forward+foo before trying user and .forward.
+<p> The set of characters that can separate a user name from its
+address extension (user+foo). See canonical(5), local(8), relocated(5)
+and virtual(5) for the effects this has on aliases, canonical,
+virtual, and relocated lookups. Basically, the software tries
+user+foo and .forward+foo before trying user and .forward. </p>
+
+<p> This implementation recognizes one delimiter character per email
+address, and one address extension per email address. </p>
+
+<p> When the recipient_delimiter set contains multiple characters
+(Postfix 2.11 and later), a user name is separated from its address
+extension by the first character that matches the recipient_delimiter
+set. </p>
+
+<p> When used in forward_path, ${recipient_delimiter} is replaced
+with the recipient delimiter that was found in the recipient email
+address (Postfix 2.11 and later), or it is replaced with the main.cf
+recipient_delimiter parameter value (Postfix 2.10 and earlier).
</p>
+<p> The recipient_delimiter is not applied to the mailer-daemon
+address, the postmaster address, or the double-bounce address. With
+the default "owner_request_special = yes" setting, the recipient_delimiter
+is also not applied to addresses with the special "owner-" prefix
+or the special "-request" suffix. </p>
+
<p>
-Example:
+Examples:
</p>
-
+
<pre>
+# Handle Postfix-style extensions.
recipient_delimiter = +
</pre>
+<pre>
+# Handle both Postfix and qmail extensions (Postfix 2.11 and later).
+recipient_delimiters = +-
+</pre>
+
+<pre>
+# Use .forward for mail without address extension, and for mail with
+# an unrecognized address extension.
+forward_path = $home/.forward${recipient_delimiter}${extension},
+ $home/.forward
+</pre>
+
%PARAM reject_code 554
<p>
<p> This feature is available in Postfix 2.8. </p>
+%PARAM postscreen_dnsbl_whitelist_threshold 0
+
+<p> Allow a remote SMTP client to skip "before" and "after 220
+greeting" protocol tests, based on its combined DNSBL score as
+defined with the postscreen_dnsbl_sites parameter. </p>
+
+<p> Specify a negative value to enable this feature. When a client
+passes the postscreen_dnsbl_whitelist_threshold without having
+failed other tests, all pending or disabled tests are flagged as
+completed with a time-to-live value equal to postscreen_dnsbl_ttl.
+When a test was already completed, its time-to-live value is updated
+if it was less than postscreen_dnsbl_ttl. </p>
+
+<p> This feature is available in Postfix 2.11. </p>
+
%PARAM postscreen_command_count_limit 20
<p> The limit on the total number of commands per SMTP session for
# .ad
# .fi
# The input format for the \fBpostmap\fR(1) command is as follows:
-# .IP "\fIpattern result\fR"
+# .IP "\fIpattern address, address, ...\fR"
# When \fIpattern\fR matches a mail address, replace it by the
-# corresponding \fIresult\fR.
+# corresponding \fIaddress\fR.
# .IP "blank lines and comments"
# Empty lines and whitespace-only lines are ignored, as
# are lines whose first non-whitespace character is a `#'.
rr_array[len] = rr;
/*
- * Shuffle resource records.
+ * Shuffle resource records. Every element has an equal chance of landing
+ * in slot 0. After that every remaining element has an equal chance of
+ * landing in slot 1, ... This is exactly n! states for n! permutations.
*/
- for (i = 0; i < len; i++) {
- r = myrand() % len;
+ for (i = 0; i < len - 1; i++) {
+ r = i + (myrand() % (len - i)); /* Victor&Son */
rr = rr_array[i];
rr_array[i] = rr_array[r];
rr_array[r] = rr;
if (*var_rcpt_delim == 0) {
bare_key = saved_ext = 0;
} else {
- bare_key = strip_addr(full_key, &saved_ext, *var_rcpt_delim);
+ bare_key = strip_addr(full_key, &saved_ext, var_rcpt_delim);
}
/*
VAR_MAIL_VERSION, DEF_MAIL_VERSION, &var_mail_version, 1, 0,
VAR_DB_TYPE, DEF_DB_TYPE, &var_db_type, 1, 0,
VAR_HASH_QUEUE_NAMES, DEF_HASH_QUEUE_NAMES, &var_hash_queue_names, 1, 0,
- VAR_RCPT_DELIM, DEF_RCPT_DELIM, &var_rcpt_delim, 0, 1,
+ VAR_RCPT_DELIM, DEF_RCPT_DELIM, &var_rcpt_delim, 0, 0,
VAR_RELAY_DOMAINS, DEF_RELAY_DOMAINS, &var_relay_domains, 0, 0,
VAR_FFLUSH_DOMAINS, DEF_FFLUSH_DOMAINS, &var_fflush_domains, 0, 0,
VAR_EXPORT_ENVIRON, DEF_EXPORT_ENVIRON, &var_export_environ, 0, 0,
#define DEF_PSC_DNSBL_THRESH 1
extern int var_psc_dnsbl_thresh;
+#define VAR_PSC_DNSBL_WTHRESH "postscreen_dnsbl_whitelist_threshold"
+#define DEF_PSC_DNSBL_WTHRESH 0
+extern int var_psc_dnsbl_wthresh;
+
#define VAR_PSC_DNSBL_ENABLE "postscreen_dnsbl_enable"
#define DEF_PSC_DNSBL_ENABLE 0
extern char *var_psc_dnsbl_enable;
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20130518"
+#define MAIL_RELEASE_DATE "20130530"
#define MAIL_VERSION_NUMBER "2.11"
#ifdef SNAPSHOT
-# define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE
+#define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE
#else
-# define MAIL_VERSION_DATE ""
+#define MAIL_VERSION_DATE ""
#endif
#ifdef NONPROD
-# define MAIL_VERSION_PROD "-nonprod"
+#define MAIL_VERSION_PROD "-nonprod"
#else
-# define MAIL_VERSION_PROD ""
+#define MAIL_VERSION_PROD ""
#endif
#define VAR_MAIL_VERSION "mail_version"
VSTRING *buffer = vstring_alloc(1);
while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
- if ((addr = split_at(STR(buffer), ' ')) == 0 || *STR(buffer) == 0)
- msg_fatal("need as input: class address");
+ addr = split_at(STR(buffer), ' ');
+ if (*STR(buffer) == 0)
+ msg_fatal("need as input: class [address]");
+ if (addr == 0)
+ addr = "";
resolve(STR(buffer), addr, &reply);
}
vstring_free(buffer);
/* SYNOPSIS
/* #include <split_addr.h>
/*
-/* char *split_addr(localpart, delimiter)
+/* char *split_addr(localpart, delimiter_set)
/* char *localpart;
-/* int delimiter;
+/* const char *delimiter_set;
/* DESCRIPTION
/* split_addr() null-terminates \fIlocalpart\fR at the first
-/* occurrence of the \fIdelimiter\fR character found, and
+/* occurrence of the \fIdelimiter\fR character(s) found, and
/* returns a pointer to the remainder.
/*
/* Reserved addresses are not split: postmaster, mailer-daemon,
/* split_addr - split address with extreme prejudice */
-char *split_addr(char *localpart, int delimiter)
+char *split_addr(char *localpart, const char *delimiter_set)
{
int len;
/*
* Backwards compatibility: don't split owner-foo or foo-request.
*/
- if (delimiter == '-' && var_ownreq_special != 0) {
+ if (strchr(delimiter_set, '-') != 0 && var_ownreq_special != 0) {
if (strncasecmp(localpart, "owner-", 6) == 0)
return (0);
if ((len = strlen(localpart) - 8) > 0
* Safe to split this address. Do not split the address if the result
* would have a null localpart.
*/
- return (delimiter == *localpart ? 0 : split_at(localpart, delimiter));
+ if ((len = strcspn(localpart, delimiter_set)) == 0 || localpart[len] == 0) {
+ return (0);
+ } else {
+ localpart[len] = 0;
+ return (localpart + len + 1);
+ }
}
/* External interface. */
-extern char *split_addr(char *, int);
+extern char *split_addr(char *, const char *);
/* LICENSE
/* .ad
/* SYNOPSIS
/* #include <strip_addr.h>
/*
-/* char *strip_addr(address, extension, delimiter)
+/* char *strip_addr(address, extension, delimiter_set)
/* const char *address;
/* char **extension;
-/* int delimiter;
+/* const char *delimiter_set;
/* DESCRIPTION
/* strip_addr() takes an address and either returns a null
/* pointer when the address contains no address extension,
/* that had to be chopped off.
/* The copy includes the recipient address delimiter.
/* The caller is expected to pass the copy to myfree().
-/* .IP delimiter
-/* Recipient address delimiter.
+/* .IP delimiter_set
+/* Set of recipient address delimiter characters.
/* SEE ALSO
/* split_addr(3) strip extension from localpart
/* LICENSE
/* strip_addr - strip extension from address */
-char *strip_addr(const char *full, char **extension, int delimiter)
+char *strip_addr(const char *full, char **extension, const char *delimiter_set)
{
char *ratsign;
char *extent;
/*
* A quick test to eliminate inputs without delimiter anywhere.
*/
- if (delimiter == 0 || strchr(full, delimiter) == 0) {
+ if (*delimiter_set == 0 || full[strcspn(full, delimiter_set)] == 0) {
stripped = saved_ext = 0;
} else {
stripped = mystrdup(full);
if ((ratsign = strrchr(stripped, '@')) != 0)
*ratsign = 0;
- if ((extent = split_addr(stripped, delimiter)) != 0) {
+ if ((extent = split_addr(stripped, delimiter_set)) != 0) {
extent -= 1;
if (extension) {
- *extent = delimiter;
+ *extent = full[strlen(stripped)];
saved_ext = mystrdup(extent);
*extent = 0;
} else
{
char *extension;
char *stripped;
- int delim = '-';
+ char* delim = "+-";
+
+#define NO_DELIM ""
/*
* Incredible. This function takes only three arguments, and the tests
* already take more lines of code than the code being tested.
*/
- stripped = strip_addr("foo", (char **) 0, 0);
+ stripped = strip_addr("foo", (char **) 0, NO_DELIM);
if (stripped != 0)
msg_panic("strip_addr botch 1");
- stripped = strip_addr("foo", &extension, 0);
+ stripped = strip_addr("foo", &extension, NO_DELIM);
if (stripped != 0)
msg_panic("strip_addr botch 2");
if (extension != 0)
if (extension != 0)
msg_panic("strip_addr botch 6");
- stripped = strip_addr("foo@bar", (char **) 0, 0);
+ stripped = strip_addr("foo@bar", (char **) 0, NO_DELIM);
if (stripped != 0)
msg_panic("strip_addr botch 7");
- stripped = strip_addr("foo@bar", &extension, 0);
+ stripped = strip_addr("foo@bar", &extension, NO_DELIM);
if (stripped != 0)
msg_panic("strip_addr botch 8");
if (extension != 0)
if (extension != 0)
msg_panic("strip_addr botch 12");
- stripped = strip_addr("foo-ext", (char **) 0, 0);
+ stripped = strip_addr("foo-ext", (char **) 0, NO_DELIM);
if (stripped != 0)
msg_panic("strip_addr botch 13");
- stripped = strip_addr("foo-ext", &extension, 0);
+ stripped = strip_addr("foo-ext", &extension, NO_DELIM);
if (stripped != 0)
msg_panic("strip_addr botch 14");
if (extension != 0)
myfree(stripped);
myfree(extension);
- stripped = strip_addr("foo-ext@bar", (char **) 0, 0);
+ stripped = strip_addr("foo-ext@bar", (char **) 0, NO_DELIM);
if (stripped != 0)
msg_panic("strip_addr botch 19");
- stripped = strip_addr("foo-ext@bar", &extension, 0);
+ stripped = strip_addr("foo-ext@bar", &extension, NO_DELIM);
if (stripped != 0)
msg_panic("strip_addr botch 20");
if (extension != 0)
myfree(stripped);
myfree(extension);
+ stripped = strip_addr("foo+ext@bar", &extension, delim);
+ if (stripped == 0)
+ msg_panic("strip_addr botch 25");
+ if (extension == 0)
+ msg_panic("strip_addr botch 26");
+ msg_info("wanted: foo+ext@bar -> %s %s", "foo@bar", "+ext");
+ msg_info("strip_addr foo+ext@bar -> %s %s", stripped, extension);
+ myfree(stripped);
+ myfree(extension);
+
return (0);
}
/* External interface. */
-extern char *strip_addr(const char *, char **, int);
+extern char *strip_addr(const char *, char **, const char *);
/* LICENSE
/* .ad
unknown: strip_addr foo-ext@bar -> foo@bar
unknown: wanted: foo-ext@bar -> foo@bar -ext
unknown: strip_addr foo-ext@bar -> foo@bar -ext
+unknown: wanted: foo+ext@bar -> foo@bar +ext
+unknown: strip_addr foo+ext@bar -> foo@bar +ext
if (alias_maps->error == 0 && owner_expansion == 0
&& (stripped_recipient = strip_addr(state.msg_attr.rcpt.address,
(char **) 0,
- *var_rcpt_delim)) != 0) {
+ var_rcpt_delim)) != 0) {
myfree(owner_alias);
FIND_OWNER(owner_alias, owner_expansion, stripped_recipient);
myfree(stripped_recipient);
FORWARD_INFO *info;
VSTREAM *cleanup;
+#define FORWARD_OPEN_RETURN(res) do { \
+ vstring_free(buffer); \
+ return (res); \
+ } while (0)
+
/*
* Contact the cleanup service and save the new mail queue id. Request
* that the cleanup service bounces bad messages to the sender so that we
*/
cleanup = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service, BLOCKING);
if (cleanup == 0)
- return (0);
+ FORWARD_OPEN_RETURN(0);
close_on_exec(vstream_fileno(cleanup), CLOSE_ON_EXEC);
if (attr_scan(cleanup, ATTR_FLAG_STRICT,
ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, buffer,
ATTR_TYPE_END) != 1) {
vstream_fclose(cleanup);
- return (0);
+ FORWARD_OPEN_RETURN(0);
}
info = (FORWARD_INFO *) mymalloc(sizeof(FORWARD_INFO));
info->cleanup = cleanup;
PASS_ATTR(cleanup, MAIL_ATTR_LOG_IDENT, request->log_ident);
PASS_ATTR(cleanup, MAIL_ATTR_RWR_CONTEXT, request->rewrite_context);
- vstring_free(buffer);
- return (info);
+ FORWARD_OPEN_RETURN(info);
}
/* forward_append - append recipient to message envelope */
/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
/* The location of the Postfix top-level queue directory.
/* .IP "\fBrecipient_delimiter (empty)\fR"
-/* The separator between user names and address extensions (user+foo).
+/* The set of characters that can separate a user name from its
+/* address extension (user+foo).
/* .IP "\fBrequire_home_directory (no)\fR"
/* Require that a \fBlocal\fR(8) recipient's home directory exists
/* before mail delivery is attempted.
static const char *local_expand_lookup(const char *name, int mode, char *ptr)
{
LOCAL_EXP *local = (LOCAL_EXP *) ptr;
+ static char rcpt_delim[2];
#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0)
local->status |= LOCAL_EXP_EXTENSION_MATCHED;
return (local->state->msg_attr.extension);
} else if (STREQ(name, "recipient_delimiter")) {
- return (*var_rcpt_delim ? var_rcpt_delim : 0);
+ rcpt_delim[0] =
+ local->state->msg_attr.local[strlen(local->state->msg_attr.user)];
+ rcpt_delim[1] = 0;
+ return (rcpt_delim[0] ? rcpt_delim : 0);
#if 0
} else if (STREQ(name, "client_hostname")) {
return (local->state->msg_attr.request->client_name);
state.msg_attr.user = mystrdup(state.msg_attr.local);
if (*var_rcpt_delim) {
state.msg_attr.extension =
- split_addr(state.msg_attr.user, *var_rcpt_delim);
+ split_addr(state.msg_attr.user, var_rcpt_delim);
if (state.msg_attr.extension && strchr(state.msg_attr.extension, '/')) {
msg_warn("%s: address with illegal extension: %s",
state.msg_attr.queue_id, state.msg_attr.local);
int status;
ssize_t ext_len;
char *ratsign;
+ int rcpt_delim;
/*
* Make verbose logging easier to understand.
* Splice in the optional unmatched address extension.
*/
if (state.msg_attr.unmatched) {
+ rcpt_delim = state.msg_attr.local[strlen(state.msg_attr.user)];
if ((ratsign = strrchr(STR(reply.recipient), '@')) == 0) {
- VSTRING_ADDCH(reply.recipient, *var_rcpt_delim);
+ VSTRING_ADDCH(reply.recipient, rcpt_delim);
vstring_strcat(reply.recipient, state.msg_attr.unmatched);
} else {
ext_len = strlen(state.msg_attr.unmatched);
if ((ratsign = strrchr(STR(reply.recipient), '@')) == 0)
msg_panic("%s: recipient @ botch", myname);
memmove(ratsign + ext_len + 1, ratsign, strlen(ratsign) + 1);
- *ratsign = *var_rcpt_delim;
+ *ratsign = rcpt_delim;
memcpy(ratsign + 1, state.msg_attr.unmatched, ext_len);
VSTRING_SKIP(reply.recipient);
}
: strlen(STR(reply.recipient)));
vstring_strncpy(queue_name, STR(reply.recipient), len);
/* Remove the address extension from the recipient localpart. */
- if (*var_rcpt_delim && split_addr(STR(queue_name), *var_rcpt_delim))
+ if (*var_rcpt_delim && split_addr(STR(queue_name), var_rcpt_delim))
vstring_truncate(queue_name, strlen(STR(queue_name)));
/* Assume the recipient domain is equivalent to nexthop. */
vstring_sprintf_append(queue_name, "@%s", STR(reply.nexthop));
/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
/* The location of the Postfix top-level queue directory.
/* .IP "\fBrecipient_delimiter (empty)\fR"
-/* The separator between user names and address extensions (user+foo).
+/* The set of characters that can separate a user name from its
+/* address extension (user+foo).
/* .IP "\fBsyslog_facility (mail)\fR"
/* The syslog facility of Postfix logging.
/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
msg_warn("no @ in recipient address: %s",
rcpt_list->info[i].address);
if (*var_rcpt_delim)
- split_addr(STR(buf), *var_rcpt_delim);
+ split_addr(STR(buf), var_rcpt_delim);
if (*STR(buf) == 0)
continue;
dict_update(PIPE_DICT_TABLE, PIPE_DICT_USER, STR(buf));
msg_warn("no @ in recipient address: %s",
rcpt_list->info[i].address);
if (*var_rcpt_delim == 0
- || (ext = split_addr(STR(buf), *var_rcpt_delim)) == 0)
+ || (ext = split_addr(STR(buf), var_rcpt_delim)) == 0)
ext = ""; /* insert null arg */
dict_update(PIPE_DICT_TABLE, PIPE_DICT_EXTENSION, ext);
}
postscreen_tests.o: ../../include/msg.h
postscreen_tests.o: ../../include/myaddrinfo.h
postscreen_tests.o: ../../include/myflock.h
+postscreen_tests.o: ../../include/name_code.h
postscreen_tests.o: ../../include/server_acl.h
postscreen_tests.o: ../../include/string_list.h
postscreen_tests.o: ../../include/sys_defs.h
/* .IP "\fBsmtpd_service_name (smtpd)\fR"
/* The internal service that \fBpostscreen\fR(8) hands off allowed
/* connections to.
+/* .PP
+/* Available in Postfix version 2.11 and later:
+/* .IP "\fBpostscreen_dnsbl_whitelist_threshold (0)\fR"
+/* Allow a remote SMTP client to skip "before" and "after 220
+/* greeting" protocol tests, based on its combined DNSBL score as
+/* defined with the postscreen_dnsbl_sites parameter.
/* AFTER-GREETING TESTS
/* .ad
/* .fi
char *var_psc_dnsbl_sites;
char *var_psc_dnsbl_reply;
int var_psc_dnsbl_thresh;
+int var_psc_dnsbl_wthresh;
char *var_psc_dnsbl_action;
int var_psc_dnsbl_ttl;
static const CONFIG_INT_TABLE int_table[] = {
VAR_PROC_LIMIT, DEF_PROC_LIMIT, &var_proc_limit, 1, 0,
VAR_PSC_DNSBL_THRESH, DEF_PSC_DNSBL_THRESH, &var_psc_dnsbl_thresh, 0, 0,
+ VAR_PSC_DNSBL_WTHRESH, DEF_PSC_DNSBL_WTHRESH, &var_psc_dnsbl_wthresh, 0, 0,
VAR_PSC_CMD_COUNT, DEF_PSC_CMD_COUNT, &var_psc_cmd_count, 1, 0,
VAR_SMTPD_CCONN_LIMIT, DEF_SMTPD_CCONN_LIMIT, &var_smtpd_cconn_limit, 0, 0,
0,
*/
#define PSC_READ_BUF_SIZE 1024
+ /*
+ * Numeric indices and symbolic names for tests whose time stamps and status
+ * flags can be accessed by numeric index.
+ */
+#define PSC_TINDX_PREGR 0 /* pregreet */
+#define PSC_TINDX_DNSBL 1 /* dnsbl */
+#define PSC_TINDX_PIPEL 2 /* pipelining */
+#define PSC_TINDX_NSMTP 3 /* non-smtp command */
+#define PSC_TINDX_BARLF 4 /* bare newline */
+#define PSC_TINDX_COUNT 5 /* number of tests */
+
+#define PSC_TNAME_PREGR "pregreet"
+#define PSC_TNAME_DNSBL "dnsbl"
+#define PSC_TNAME_PIPEL "pipelining"
+#define PSC_TNAME_NSMTP "non-smtp command"
+#define PSC_TNAME_BARLF "bare newline"
+
+#define PSC_TINDX_BYTNAME(tname) (PSC_TINDX_ ## tname)
+
/*
* Per-session state.
*/
/* Test context. */
struct timeval start_time; /* start of current test */
const char *test_name; /* name of current test */
- /* Before-handshake tests. */
- time_t pregr_stamp; /* pregreet expiration time */
- time_t dnsbl_stamp; /* dnsbl expiration time */
+ time_t expire_time[PSC_TINDX_COUNT]; /* per-test expiration */
VSTRING *dnsbl_reply; /* dnsbl reject text */
+ int dnsbl_score; /* saved DNSBL score */
+ const char *dnsbl_name; /* DNSBL name with largest weight */
int dnsbl_index; /* dnsbl request index */
- time_t penal_stamp; /* penalty expiration time */
- /* Built-in SMTP protocol engine. */
- time_t pipel_stamp; /* pipelining expiration time */
- time_t nsmtp_stamp; /* non-smtp command expiration time */
- time_t barlf_stamp; /* bare newline expiration time */
const char *rcpt_reply; /* how to reject recipients */
int command_count; /* error + junk command count */
const char *protocol; /* SMTP or ESMTP */
const char *where; /* SMTP protocol state */
} PSC_STATE;
+ /*
+ * Emulate legacy ad-hoc variables on top of indexable time stamps. This
+ * avoids massive scar tissue during initial feature development.
+ */
+#define pregr_stamp expire_time[PSC_TINDX_PREGR]
+#define dnsbl_stamp expire_time[PSC_TINDX_DNSBL]
+#define pipel_stamp expire_time[PSC_TINDX_PIPEL]
+#define nsmtp_stamp expire_time[PSC_TINDX_NSMTP]
+#define barlf_stamp expire_time[PSC_TINDX_BARLF]
+
+ /*
+ * Special expiration time values.
+ */
#define PSC_TIME_STAMP_NEW (0) /* test was never passed */
#define PSC_TIME_STAMP_DISABLED (1) /* never passed but disabled */
#define PSC_TIME_STAMP_INVALID (-1) /* must not be cached */
+ /*
+ * Status flags.
+ */
#define PSC_STATE_FLAG_NOFORWARD (1<<0) /* don't forward this session */
#define PSC_STATE_FLAG_USING_TLS (1<<1) /* using the TLS proxy */
#define PSC_STATE_FLAG_UNUSED2 (1<<2) /* use me! */
#define PSC_STATE_FLAG_HANGUP (1<<5) /* NOT a test failure */
#define PSC_STATE_FLAG_SMTPD_X21 (1<<6) /* hang up after command */
#define PSC_STATE_FLAG_WLIST_FAIL (1<<7) /* do not whitelist */
+#define PSC_STATE_FLAG_TEST_BASE (8) /* start of indexable flags */
/*
+ * Tests whose flags and expiration time can be accessed by numerical index.
+ *
* Important: every MUMBLE_TODO flag must have a MUMBLE_PASS flag, such that
* MUMBLE_PASS == PSC_STATE_FLAGS_TODO_TO_PASS(MUMBLE_TODO).
*
* the result was ignored. MUMBLE_FAIL, on the other hand, is always final.
* We use MUMBLE_SKIP to indicate that a decision was either "fail" or
* forced "pass".
+ *
+ * The difference between DONE and SKIP is in the beholder's eye. These flags
+ * share the same bit.
*/
#define PSC_STATE_FLAGS_TODO_TO_PASS(todo_flags) ((todo_flags) >> 1)
#define PSC_STATE_FLAGS_TODO_TO_DONE(todo_flags) ((todo_flags) << 1)
-#define PSC_STATE_FLAG_PENAL_UPDATE (1<<6) /* save new penalty */
-#define PSC_STATE_FLAG_PENAL_FAIL (1<<7) /* penalty is active */
-
-#define PSC_STATE_FLAG_PREGR_FAIL (1<<8) /* failed pregreet test */
-#define PSC_STATE_FLAG_PREGR_PASS (1<<9) /* passed pregreet test */
-#define PSC_STATE_FLAG_PREGR_TODO (1<<10) /* pregreet test expired */
-#define PSC_STATE_FLAG_PREGR_DONE (1<<11) /* decision is final */
-
-#define PSC_STATE_FLAG_DNSBL_FAIL (1<<12) /* failed DNSBL test */
-#define PSC_STATE_FLAG_DNSBL_PASS (1<<13) /* passed DNSBL test */
-#define PSC_STATE_FLAG_DNSBL_TODO (1<<14) /* DNSBL test expired */
-#define PSC_STATE_FLAG_DNSBL_DONE (1<<15) /* decision is final */
-
- /* Room here for one more after-handshake test. */
+#define PSC_STATE_FLAG_SHIFT_FAIL (0) /* failed test */
+#define PSC_STATE_FLAG_SHIFT_PASS (1) /* passed test */
+#define PSC_STATE_FLAG_SHIFT_TODO (2) /* expired test */
+#define PSC_STATE_FLAG_SHIFT_DONE (3) /* decision is final */
+#define PSC_STATE_FLAG_SHIFT_SKIP (3) /* action is already logged */
+#define PSC_STATE_FLAG_SHIFT_STRIDE (4) /* nr of flags per test */
-#define PSC_STATE_FLAG_PIPEL_FAIL (1<<20) /* failed pipelining test */
-#define PSC_STATE_FLAG_PIPEL_PASS (1<<21) /* passed pipelining test */
-#define PSC_STATE_FLAG_PIPEL_TODO (1<<22) /* pipelining test expired */
-#define PSC_STATE_FLAG_PIPEL_SKIP (1<<23) /* action is already logged */
+#define PSC_STATE_FLAG_SHIFT_BYFNAME(fname) (PSC_STATE_FLAG_SHIFT_ ## fname)
-#define PSC_STATE_FLAG_NSMTP_FAIL (1<<24) /* failed non-SMTP test */
-#define PSC_STATE_FLAG_NSMTP_PASS (1<<25) /* passed non-SMTP test */
-#define PSC_STATE_FLAG_NSMTP_TODO (1<<26) /* non-SMTP test expired */
-#define PSC_STATE_FLAG_NSMTP_SKIP (1<<27) /* action is already logged */
+ /*
+ * Indexable per-test flags. These are used for DNS whitelisting multiple
+ * tests, without needing per-test ad-hoc code.
+ */
+#define PSC_STATE_FLAG_BYTINDX_FNAME(tindx, fname) \
+ (1U << (PSC_STATE_FLAG_TEST_BASE \
+ + PSC_STATE_FLAG_SHIFT_STRIDE * (tindx) \
+ + PSC_STATE_FLAG_SHIFT_BYFNAME(fname)))
+
+#define PSC_STATE_FLAG_BYTINDX_FAIL(tindx) \
+ PSC_STATE_FLAG_BYTINDX_FNAME((tindx), FAIL)
+#define PSC_STATE_FLAG_BYTINDX_PASS(tindx) \
+ PSC_STATE_FLAG_BYTINDX_FNAME((tindx), PASS)
+#define PSC_STATE_FLAG_BYTINDX_TODO(tindx) \
+ PSC_STATE_FLAG_BYTINDX_FNAME((tindx), TODO)
+#define PSC_STATE_FLAG_BYTINDX_DONE(tindx) \
+ PSC_STATE_FLAG_BYTINDX_FNAME((tindx), DONE)
+#define PSC_STATE_FLAG_BYTINDX_SKIP(tindx) \
+ PSC_STATE_FLAG_BYTINDX_FNAME((tindx), SKIP)
-#define PSC_STATE_FLAG_BARLF_FAIL (1<<28) /* failed bare newline test */
-#define PSC_STATE_FLAG_BARLF_PASS (1<<29) /* passed bare newline test */
-#define PSC_STATE_FLAG_BARLF_TODO (1<<30) /* bare newline test expired */
-#define PSC_STATE_FLAG_BARLF_SKIP (1<<31) /* action is already logged */
+ /*
+ * Flags with distinct names. These are used in the per-test ad-hoc code.
+ */
+#define PSC_STATE_FLAG_BYTNAME_FNAME(tname, fname) \
+ (1U << (PSC_STATE_FLAG_TEST_BASE \
+ + PSC_STATE_FLAG_SHIFT_STRIDE * PSC_TINDX_BYTNAME(tname) \
+ + PSC_STATE_FLAG_SHIFT_BYFNAME(fname)))
+
+#define PSC_STATE_FLAG_PREGR_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(PREGR, FAIL)
+#define PSC_STATE_FLAG_PREGR_PASS PSC_STATE_FLAG_BYTNAME_FNAME(PREGR, PASS)
+#define PSC_STATE_FLAG_PREGR_TODO PSC_STATE_FLAG_BYTNAME_FNAME(PREGR, TODO)
+#define PSC_STATE_FLAG_PREGR_DONE PSC_STATE_FLAG_BYTNAME_FNAME(PREGR, DONE)
+
+#define PSC_STATE_FLAG_DNSBL_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(DNSBL, FAIL)
+#define PSC_STATE_FLAG_DNSBL_PASS PSC_STATE_FLAG_BYTNAME_FNAME(DNSBL, PASS)
+#define PSC_STATE_FLAG_DNSBL_TODO PSC_STATE_FLAG_BYTNAME_FNAME(DNSBL, TODO)
+#define PSC_STATE_FLAG_DNSBL_DONE PSC_STATE_FLAG_BYTNAME_FNAME(DNSBL, DONE)
+
+#define PSC_STATE_FLAG_PIPEL_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(PIPEL, FAIL)
+#define PSC_STATE_FLAG_PIPEL_PASS PSC_STATE_FLAG_BYTNAME_FNAME(PIPEL, PASS)
+#define PSC_STATE_FLAG_PIPEL_TODO PSC_STATE_FLAG_BYTNAME_FNAME(PIPEL, TODO)
+#define PSC_STATE_FLAG_PIPEL_SKIP PSC_STATE_FLAG_BYTNAME_FNAME(PIPEL, SKIP)
+
+#define PSC_STATE_FLAG_NSMTP_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(NSMTP, FAIL)
+#define PSC_STATE_FLAG_NSMTP_PASS PSC_STATE_FLAG_BYTNAME_FNAME(NSMTP, PASS)
+#define PSC_STATE_FLAG_NSMTP_TODO PSC_STATE_FLAG_BYTNAME_FNAME(NSMTP, TODO)
+#define PSC_STATE_FLAG_NSMTP_SKIP PSC_STATE_FLAG_BYTNAME_FNAME(NSMTP, SKIP)
+
+#define PSC_STATE_FLAG_BARLF_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(BARLF, FAIL)
+#define PSC_STATE_FLAG_BARLF_PASS PSC_STATE_FLAG_BYTNAME_FNAME(BARLF, PASS)
+#define PSC_STATE_FLAG_BARLF_TODO PSC_STATE_FLAG_BYTNAME_FNAME(BARLF, TODO)
+#define PSC_STATE_FLAG_BARLF_SKIP PSC_STATE_FLAG_BYTNAME_FNAME(BARLF, SKIP)
/*
* Aggregates for individual tests.
#define PSC_STATE_MASK_BARLF_TODO_FAIL \
(PSC_STATE_FLAG_BARLF_TODO | PSC_STATE_FLAG_BARLF_FAIL)
+#define PSC_STATE_MASK_PREGR_TODO_DONE \
+ (PSC_STATE_FLAG_PREGR_TODO | PSC_STATE_FLAG_PREGR_DONE)
#define PSC_STATE_MASK_PIPEL_TODO_SKIP \
(PSC_STATE_FLAG_PIPEL_TODO | PSC_STATE_FLAG_PIPEL_SKIP)
#define PSC_STATE_MASK_NSMTP_TODO_SKIP \
#define PSC_STATE_MASK_BARLF_TODO_SKIP \
(PSC_STATE_FLAG_BARLF_TODO | PSC_STATE_FLAG_BARLF_SKIP)
+#define PSC_STATE_MASK_PREGR_FAIL_DONE \
+ (PSC_STATE_FLAG_PREGR_FAIL | PSC_STATE_FLAG_PREGR_DONE)
+
#define PSC_STATE_MASK_PIPEL_TODO_PASS_FAIL \
(PSC_STATE_MASK_PIPEL_TODO_FAIL | PSC_STATE_FLAG_PIPEL_PASS)
#define PSC_STATE_MASK_NSMTP_TODO_PASS_FAIL \
* Super-aggregates for all tests combined.
*/
#define PSC_STATE_MASK_ANY_FAIL \
- (PSC_STATE_FLAG_BLIST_FAIL | PSC_STATE_FLAG_PENAL_FAIL | \
+ (PSC_STATE_FLAG_BLIST_FAIL | \
PSC_STATE_MASK_EARLY_FAIL | PSC_STATE_MASK_SMTPD_FAIL | \
PSC_STATE_FLAG_WLIST_FAIL)
(PSC_STATE_MASK_ANY_TODO | PSC_STATE_MASK_ANY_FAIL)
#define PSC_STATE_MASK_ANY_UPDATE \
- (PSC_STATE_MASK_ANY_PASS | PSC_STATE_FLAG_PENAL_UPDATE)
+ (PSC_STATE_MASK_ANY_PASS)
/*
* Meta-commands for state->where that reflect the initial command processor
* postscreen_tests.c
*/
#define PSC_INIT_TESTS(dst) do { \
+ time_t *_it_stamp_p; \
(dst)->flags = 0; \
- (dst)->pregr_stamp = PSC_TIME_STAMP_INVALID; \
- (dst)->dnsbl_stamp = PSC_TIME_STAMP_INVALID; \
- (dst)->pipel_stamp = PSC_TIME_STAMP_INVALID; \
- (dst)->barlf_stamp = PSC_TIME_STAMP_INVALID; \
- (dst)->penal_stamp = PSC_TIME_STAMP_INVALID; \
+ for (_it_stamp_p = (dst)->expire_time; \
+ _it_stamp_p < (dst)->expire_time + PSC_TINDX_COUNT; \
+ _it_stamp_p++) \
+ *_it_stamp_p = PSC_TIME_STAMP_INVALID; \
} while (0)
#define PSC_BEGIN_TESTS(state, name) do { \
(state)->test_name = (name); \
extern char *psc_print_tests(VSTRING *, PSC_STATE *);
extern char *psc_print_grey_key(VSTRING *, const char *, const char *,
const char *, const char *);
+extern const char *psc_test_name(int);
#define PSC_MIN(x, y) ((x) < (y) ? (x) : (y))
#define PSC_MAX(x, y) ((x) > (y) ? (x) : (y))
#include <sys_defs.h>
#include <sys/socket.h>
+#include <limits.h>
/* Utility library. */
static char *psc_teaser_greeting;
static VSTRING *psc_escape_buf;
+/* psc_whitelist_non_dnsbl - whitelist pending non-dnsbl tests */
+
+static void psc_whitelist_non_dnsbl(PSC_STATE *state)
+{
+ time_t now;
+ int tindx;
+
+ /*
+ * If no tests failed (we can't undo those), and if the whitelist
+ * threshold is met, flag non-dnsbl tests that are pending or disabled as
+ * successfully completed, and set their expiration times equal to the
+ * DNSBL expiration time, except for tests that would expire later.
+ *
+ * Why flag disabled tests as passed? When a disabled test is turned on,
+ * postscreen should not apply that test to clients that are already
+ * whitelisted based on their combined DNSBL score.
+ */
+ if ((state->flags & PSC_STATE_MASK_ANY_FAIL) == 0
+ && state->dnsbl_score < var_psc_dnsbl_thresh
+ && var_psc_dnsbl_wthresh < 0
+ && state->dnsbl_score <= var_psc_dnsbl_wthresh) {
+ now = event_time();
+ for (tindx = 0; tindx < PSC_TINDX_COUNT; tindx++) {
+ if (tindx == PSC_TINDX_DNSBL)
+ continue;
+ if ((state->flags & PSC_STATE_FLAG_BYTINDX_TODO(tindx))
+ && !(state->flags & PSC_STATE_FLAG_BYTINDX_PASS(tindx))) {
+ if (msg_verbose)
+ msg_info("skip %s test for [%s]:%s",
+ psc_test_name(tindx), PSC_CLIENT_ADDR_PORT(state));
+ /* Wrong for deep protocol tests, but we disable those. */
+ state->flags |= PSC_STATE_FLAG_BYTINDX_DONE(tindx);
+ /* This also disables pending deep protocol tests. */
+ state->flags |= PSC_STATE_FLAG_BYTINDX_PASS(tindx);
+ }
+ /* Update expiration even if the test was completed or disabled. */
+ if (state->expire_time[tindx] < now + var_psc_dnsbl_ttl)
+ state->expire_time[tindx] = now + var_psc_dnsbl_ttl;
+ }
+ }
+}
+
/* psc_early_event - handle pre-greet, EOF, and DNSBL results. */
static void psc_early_event(int event, char *context)
PSC_STATE *state = (PSC_STATE *) context;
char read_buf[PSC_READ_BUF_SIZE];
int read_count;
- int dnsbl_score;
DELTA_TIME elapsed;
- const char *dnsbl_name;
if (msg_verbose > 1)
msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s",
switch (event) {
/*
- * We reached the end of the early tests time limit.
+ * We either reached the end of the early tests time limit, or all
+ * early tests completed before the pregreet timer would go off.
*/
case EVENT_TIME:
/*
* Check if the SMTP client spoke before its turn.
*/
- if ((state->flags & PSC_STATE_MASK_PREGR_TODO_FAIL)
- == PSC_STATE_FLAG_PREGR_TODO) {
+ if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0
+ && (state->flags & PSC_STATE_MASK_PREGR_FAIL_DONE) == 0) {
state->pregr_stamp = event_time() + var_psc_pregr_ttl;
PSC_PASS_SESSION_STATE(state, "pregreet test",
PSC_STATE_FLAG_PREGR_PASS);
}
/*
+ * Collect the DNSBL score, and whitelist other tests if applicable.
+ * Note: this score will be partial when some DNS lookup did not
+ * complete before the pregreet timer expired.
+ *
* If the client is DNS blocklisted, drop the connection, send the
* client to a dummy protocol engine, or continue to the next test.
*/
#define PSC_DNSBL_FORMAT \
"%s 5.7.1 Service unavailable; client [%s] blocked using %s\r\n"
+#define NO_DNSBL_SCORE INT_MAX
if (state->flags & PSC_STATE_FLAG_DNSBL_TODO) {
- dnsbl_score =
- psc_dnsbl_retrieve(state->smtp_client_addr, &dnsbl_name,
- state->dnsbl_index);
- if (dnsbl_score < var_psc_dnsbl_thresh) {
+ if (state->dnsbl_score == NO_DNSBL_SCORE) {
+ state->dnsbl_score =
+ psc_dnsbl_retrieve(state->smtp_client_addr,
+ &state->dnsbl_name,
+ state->dnsbl_index);
+ if (var_psc_dnsbl_wthresh < 0)
+ psc_whitelist_non_dnsbl(state);
+ }
+ if (state->dnsbl_score < var_psc_dnsbl_thresh) {
state->dnsbl_stamp = event_time() + var_psc_dnsbl_ttl;
PSC_PASS_SESSION_STATE(state, "dnsbl test",
PSC_STATE_FLAG_DNSBL_PASS);
} else {
msg_info("DNSBL rank %d for [%s]:%s",
- dnsbl_score, PSC_CLIENT_ADDR_PORT(state));
+ state->dnsbl_score, PSC_CLIENT_ADDR_PORT(state));
PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNSBL_FAIL);
switch (psc_dnsbl_action) {
case PSC_ACT_DROP:
state->dnsbl_reply = vstring_sprintf(vstring_alloc(100),
PSC_DNSBL_FORMAT, "521",
- state->smtp_client_addr, dnsbl_name);
+ state->smtp_client_addr,
+ state->dnsbl_name);
PSC_DROP_SESSION_STATE(state, STR(state->dnsbl_reply));
return;
case PSC_ACT_ENFORCE:
state->dnsbl_reply = vstring_sprintf(vstring_alloc(100),
PSC_DNSBL_FORMAT, "550",
- state->smtp_client_addr, dnsbl_name);
+ state->smtp_client_addr,
+ state->dnsbl_name);
PSC_ENFORCE_SESSION_STATE(state, STR(state->dnsbl_reply));
break;
case PSC_ACT_IGNORE:
* Pass the connection to a real SMTP server, or enter the dummy
* engine for deep tests.
*/
- if (state->flags & (PSC_STATE_FLAG_NOFORWARD | PSC_STATE_MASK_SMTPD_TODO))
+ if ((state->flags & PSC_STATE_FLAG_NOFORWARD) != 0
+ || ((state->flags & PSC_STATE_MASK_SMTPD_PASS)
+ != PSC_STATE_FLAGS_TODO_TO_PASS(state->flags & PSC_STATE_MASK_SMTPD_TODO)))
psc_smtpd_tests(state);
else
psc_conclude(state);
if ((read_count = recv(vstream_fileno(state->smtp_client_stream),
read_buf, sizeof(read_buf) - 1, MSG_PEEK)) <= 0) {
/* Avoid memory leak. */
- if (state->flags & PSC_STATE_FLAG_DNSBL_TODO)
- (void) psc_dnsbl_retrieve(state->smtp_client_addr, &dnsbl_name,
+ if (state->dnsbl_score == NO_DNSBL_SCORE
+ && (state->flags & PSC_STATE_FLAG_DNSBL_TODO))
+ (void) psc_dnsbl_retrieve(state->smtp_client_addr,
+ &state->dnsbl_name,
state->dnsbl_index);
/* XXX Wait for DNS replies to come in. */
psc_hangup_event(state);
switch (psc_pregr_action) {
case PSC_ACT_DROP:
/* Avoid memory leak. */
- if (state->flags & PSC_STATE_FLAG_DNSBL_TODO)
- (void) psc_dnsbl_retrieve(state->smtp_client_addr, &dnsbl_name,
+ if (state->dnsbl_score == NO_DNSBL_SCORE
+ && (state->flags & PSC_STATE_FLAG_DNSBL_TODO))
+ (void) psc_dnsbl_retrieve(state->smtp_client_addr,
+ &state->dnsbl_name,
state->dnsbl_index);
PSC_DROP_SESSION_STATE(state, "521 5.5.1 Protocol error\r\n");
return;
if (msg_verbose)
msg_info("%s: notify [%s]:%s", myname, PSC_CLIENT_ADDR_PORT(state));
+ /*
+ * Collect the DNSBL score, and whitelist other tests if applicable.
+ */
+ state->dnsbl_score =
+ psc_dnsbl_retrieve(state->smtp_client_addr, &state->dnsbl_name,
+ state->dnsbl_index);
+ if (var_psc_dnsbl_wthresh < 0)
+ psc_whitelist_non_dnsbl(state);
+
/*
* Terminate the greet delay if we're just waiting for DNSBL lookup to
* complete. Don't call psc_early_event directly, that would result in a
(char *) state);
else
state->dnsbl_index = -1;
+ state->dnsbl_score = NO_DNSBL_SCORE;
/*
* Wait for the client to respond or for DNS lookup to complete.
/* unused */
"WLIST_FAIL", PSC_STATE_FLAG_WLIST_FAIL,
- "PENAL_UPDATE", PSC_STATE_FLAG_PENAL_UPDATE,
- "PENAL_FAIL", PSC_STATE_FLAG_PENAL_FAIL,
-
"PREGR_FAIL", PSC_STATE_FLAG_PREGR_FAIL,
"PREGR_PASS", PSC_STATE_FLAG_PREGR_PASS,
"PREGR_TODO", PSC_STATE_FLAG_PREGR_TODO,
/* const char *helo;
/* const char *sender;
/* const char *rcpt;
+/*
+/* const char *psc_test_name(tindx)
+/* int tindx;
/* DESCRIPTION
/* The functions in this module overwrite the per-test expiration
/* time stamps and all flags bits. Some functions are implemented
/* This may modify the time stamps for disabled tests.
/*
/* psc_print_grey_key() prints a greylist lookup key.
+/*
+/* psc_test_name() returns the name for the specified text
+/* index.
/* LICENSE
/* .ad
/* .fi
#include <sys_defs.h>
#include <stdio.h> /* sscanf */
+#include <stdlib.h> /* strtoul */
/* Utility library. */
#include <msg.h>
+#include <name_code.h>
/* Global library. */
state->pipel_stamp = PSC_TIME_STAMP_NEW;
state->nsmtp_stamp = PSC_TIME_STAMP_NEW;
state->barlf_stamp = PSC_TIME_STAMP_NEW;
- state->penal_stamp = PSC_TIME_STAMP_NEW;
/*
* Don't flag disabled tests as "todo", because there would be no way to
const char *stamp_str,
time_t time_value)
{
- unsigned long pregr_stamp;
- unsigned long dnsbl_stamp;
- unsigned long pipel_stamp;
- unsigned long nsmtp_stamp;
- unsigned long barlf_stamp;
- unsigned long penal_stamp;
-
-#ifdef NONPROD
- time_t penalty_left;
-
-#endif
+ const char *start = stamp_str;
+ char *cp;
+ time_t *time_stamps = state->expire_time;
+ time_t *sp;
/*
* We don't know what tests have expired or have never passed.
* enabled tests, but the remote SMTP client has not yet passed all those
* tests.
*/
- switch (sscanf(stamp_str, "%lu;%lu;%lu;%lu;%lu;%lu",
- &pregr_stamp, &dnsbl_stamp, &pipel_stamp, &nsmtp_stamp,
- &barlf_stamp, &penal_stamp)) {
- case 0:
- pregr_stamp = PSC_TIME_STAMP_DISABLED;
- case 1:
- dnsbl_stamp = PSC_TIME_STAMP_DISABLED;
- case 2:
- pipel_stamp = PSC_TIME_STAMP_DISABLED;
- case 3:
- nsmtp_stamp = PSC_TIME_STAMP_DISABLED;
- case 4:
- barlf_stamp = PSC_TIME_STAMP_DISABLED;
- case 5:
- penal_stamp = PSC_TIME_STAMP_DISABLED;
- default:
- break;
+ for (sp = time_stamps; sp < time_stamps + PSC_TINDX_COUNT; sp++) {
+ *sp = strtoul(start, &cp, 10);
+ if (*start == 0 || (*cp != '\0' && *cp != ';') || errno == ERANGE)
+ *sp = PSC_TIME_STAMP_DISABLED;
+ if (*sp == PSC_TIME_STAMP_NEW)
+ state->flags |= PSC_STATE_FLAG_NEW;
+ if (msg_verbose)
+ msg_info("%s -> %lu", start, (unsigned long) *sp);
+ if (*cp == ';')
+ start = cp + 1;
+ else
+ start = cp;
}
- state->pregr_stamp = pregr_stamp;
- state->dnsbl_stamp = dnsbl_stamp;
- state->pipel_stamp = pipel_stamp;
- state->nsmtp_stamp = nsmtp_stamp;
- state->barlf_stamp = barlf_stamp;
- state->penal_stamp = penal_stamp;
-
- if (pregr_stamp == PSC_TIME_STAMP_NEW
- || dnsbl_stamp == PSC_TIME_STAMP_NEW
- || pipel_stamp == PSC_TIME_STAMP_NEW
- || nsmtp_stamp == PSC_TIME_STAMP_NEW
- || barlf_stamp == PSC_TIME_STAMP_NEW)
- state->flags |= PSC_STATE_FLAG_NEW;
/*
* Don't flag disabled tests as "todo", because there would be no way to
state->flags |= PSC_STATE_FLAG_DNSBL_TODO;
}
#endif
-
- /*
- * Apply unexpired penalty for past behavior.
- *
- * XXX Before we can drop connections, change this function to return
- * success/fail, to inform the caller that the state object no longer
- * exists.
- */
-#ifdef NONPROD
- if ((penalty_left = state->penal_stamp - event_time()) > 0) {
- msg_info("PENALTY %ld for %s",
- (long) penalty_left, state->smtp_client_addr);
- PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_PENAL_FAIL);
-#if 0
- switch (psc_penal_action) {
- case PSC_ACT_DROP:
- PSC_DROP_SESSION_STATE(state,
- "421 4.3.2 Service currently unavailable\r\n");
- break;
- case PSC_ACT_ENFORCE:
-#endif
- PSC_ENFORCE_SESSION_STATE(state,
- "450 4.3.2 Service currently unavailable\r\n");
-#if 0
- break;
- case PSC_ACT_IGNORE:
- PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_PENAL_FAIL);
- break;
- default:
- msg_panic("%s: unknown penalty action value %d",
- myname, psc_penal_action);
- }
-#endif
- }
-#endif /* NONPROD */
}
/* psc_print_tests - print postscreen cache record */
if ((state->flags & PSC_STATE_MASK_ANY_UPDATE) == 0)
msg_panic("%s: attempt to save a no-update record", myname);
- /*
- * Don't record a client as "passed" while subject to penalty. Be sure to
- * produce correct PASS OLD/NEW logging.
- *
- * XXX This needs to be refined - we should not reset the result of tests
- * that were passed in previous sessions, otherwise a client may never
- * pass a multi-stage test such as greylisting. One solution is to keep
- * the original and updated time stamps around, and to save an updated
- * time stamp only when the corresponding "pass" flag is raised.
- */
-#ifdef NONPROD
- if (state->flags & PSC_STATE_FLAG_PENAL_FAIL) {
- state->pregr_stamp = state->dnsbl_stamp = state->pipel_stamp =
- state->nsmtp_stamp = state->barlf_stamp =
- ((state->flags & PSC_STATE_FLAG_NEW) ?
- PSC_TIME_STAMP_NEW : PSC_TIME_STAMP_DISABLED);
- }
-#endif
-
/*
* Give disabled tests a dummy time stamp so that we don't log a client
* with "pass new" when some disabled test becomes enabled at some later
if (var_psc_barlf_enable == 0 && state->barlf_stamp == PSC_TIME_STAMP_NEW)
state->barlf_stamp = PSC_TIME_STAMP_DISABLED;
- vstring_sprintf(buf, "%lu;%lu;%lu;%lu;%lu;%lu",
+ vstring_sprintf(buf, "%lu;%lu;%lu;%lu;%lu",
(unsigned long) state->pregr_stamp,
(unsigned long) state->dnsbl_stamp,
(unsigned long) state->pipel_stamp,
(unsigned long) state->nsmtp_stamp,
- (unsigned long) state->barlf_stamp,
- (unsigned long) state->penal_stamp);
+ (unsigned long) state->barlf_stamp);
return (STR(buf));
}
return (STR(vstring_sprintf(buf, "%s/%s/%s/%s",
client, helo, sender, rcpt)));
}
+
+/* psc_test_name - map test index to symbolic name */
+
+const char *psc_test_name(int tindx)
+{
+ const char *myname = "psc_test_name";
+ const NAME_CODE test_name_map[] = {
+ PSC_TNAME_PREGR, PSC_TINDX_PREGR,
+ PSC_TNAME_DNSBL, PSC_TINDX_DNSBL,
+ PSC_TNAME_PIPEL, PSC_TINDX_PIPEL,
+ PSC_TNAME_NSMTP, PSC_TINDX_NSMTP,
+ PSC_TNAME_BARLF, PSC_TINDX_BARLF,
+ 0, -1,
+ };
+ const char *result;
+
+ if ((result = str_name_code(test_name_map, tindx)) == 0)
+ msg_panic("%s: bad index %d", myname, tindx);
+ return (result);
+}
TLS_SESS_STATE *tls_context; /* Session TLS context */
TLS_DANE *dane; /* DANE TLSA validation structure */
TLS_DANE *ddane; /* DANE TLSA from DNS */
- char *grade; /* Minimum cipher grade */
- char *protocols; /* Protocol inclusion/exclusion */
+ char *grade; /* Minimum cipher grade */
+ char *protocols; /* Protocol inclusion/exclusion */
#endif
OPTIONS options; /* JCL */
} STATE;
/*
/* Viktor Dukhovni
/*--*/
-
: strlen(STR(reply.recipient)));
vstring_strncpy(queue_name, STR(reply.recipient), len);
/* Remove the address extension from the recipient localpart. */
- if (*var_rcpt_delim && split_addr(STR(queue_name), *var_rcpt_delim))
+ if (*var_rcpt_delim && split_addr(STR(queue_name), var_rcpt_delim))
vstring_truncate(queue_name, strlen(STR(queue_name)));
/* Assume the recipient domain is equivalent to nexthop. */
vstring_sprintf_append(queue_name, "@%s", STR(reply.nexthop));
/* not contain RFC 822 style comments or phrases.
/* .PP
/* Available in Postfix version 2.1 and later:
-/* .IP "\fBresolve_null_domain (no)\fR"
-/* Resolve an address that ends in the "@" null domain as if the
-/* local hostname were specified, instead of rejecting the address as
-/* invalid.
/* .IP "\fBsmtpd_reject_unlisted_sender (no)\fR"
/* Request that the Postfix SMTP server rejects mail from unknown
/* sender addresses, even when no explicit reject_unlisted_sender
/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
/* The location of the Postfix top-level queue directory.
/* .IP "\fBrecipient_delimiter (empty)\fR"
-/* The separator between user names and address extensions (user+foo).
+/* The set of characters that can separate a user name from its
+/* address extension (user+foo).
/* .IP "\fBsmtpd_banner ($myhostname ESMTP $mail_name)\fR"
/* The text that follows the 220 status code in the SMTP greeting
/* banner.
if (*var_rcpt_delim == 0) {
bare_addr = 0;
} else {
- bare_addr = strip_addr(addr, (char **) 0, *var_rcpt_delim);
+ bare_addr = strip_addr(addr, (char **) 0, var_rcpt_delim);
}
#define CHECK_MAIL_ACCESS_RETURN(x) \
/* int expect;
/* const char *format;
/*
-/* void smtpd_proxy_disconnect(state)
-/* SMTPD_STATE *state;
-/*
/* void smtpd_proxy_free(state)
/* SMTPD_STATE *state;
/*
/* In case of error, proxy->cmd() updates the state->error_mask
/* and state->err fields.
/*
-/* smtpd_proxy_disconnect() disconnects from a proxy server.
-/* The last proxy server reply or error description remains
-/* available via the proxy->buffer field.
-/*
/* smtpd_proxy_free() destroys a proxy server handle and resets
/* the state->proxy field.
/*
#include <mymalloc.h>
#include <stringops.h>
#include <vstring.h>
-#include <events.h> /* event_time() */
+#include <events.h> /* event_time() */
#include <timecmp.h>
#include <ctable.h>
/* tls_dane_verbose - enable/disable verbose logging */
-void tls_dane_verbose(int on)
+void tls_dane_verbose(int on)
{
dane_verbose = on;
}
/* tls_dane_avail - check for availability of dane required digests */
-int tls_dane_avail(void)
+int tls_dane_avail(void)
{
-#ifdef TLSEXT_MAXLEN_host_name /* DANE mandates client SNI. */
+#ifdef TLSEXT_MAXLEN_host_name /* DANE mandates client SNI. */
static int avail = -1;
const EVP_MD *sha256md;
const EVP_MD *sha512md;
static NAME_MASK ta_dgsts[] = {
- TLS_DANE_CC, TLS_DANE_ENABLE_CC,
- TLS_DANE_TAA, TLS_DANE_ENABLE_TAA,
+ TLS_DANE_CC, TLS_DANE_ENABLE_CC,
+ TLS_DANE_TAA, TLS_DANE_ENABLE_TAA,
0,
};
return (avail = 1);
#else
- return (0);
+ return (0);
#endif
}
/* tls_dane_flush - flush the cache */
-void tls_dane_flush(void)
+void tls_dane_flush(void)
{
if (dane_cache)
ctable_free(dane_cache);
TLS_DANE *tls_dane_alloc(int flags)
{
- TLS_DANE *dane = (TLS_DANE *)mymalloc(sizeof(*dane));
+ TLS_DANE *dane = (TLS_DANE *) mymalloc(sizeof(*dane));
dane->ta = 0;
dane->ee = 0;
static void ta_cert_insert(TLS_DANE *d, X509 *x)
{
- TLS_CERTS *new = (TLS_CERTS *)mymalloc(sizeof(*new));
+ TLS_CERTS *new = (TLS_CERTS *) mymalloc(sizeof(*new));
CRYPTO_add(&x->references, 1, CRYPTO_LOCK_X509);
new->cert = x;
for (head = d->certs; head; head = next) {
next = head->next;
X509_free(head->cert);
- myfree((char *)head);
+ myfree((char *) head);
}
}
static void ta_pkey_insert(TLS_DANE *d, EVP_PKEY *k)
{
- TLS_PKEYS *new = (TLS_PKEYS *)mymalloc(sizeof(*new));
+ TLS_PKEYS *new = (TLS_PKEYS *) mymalloc(sizeof(*new));
CRYPTO_add(&k->references, 1, CRYPTO_LOCK_EVP_PKEY);
new->pkey = k;
for (head = d->pkeys; head; head = next) {
next = head->next;
EVP_PKEY_free(head->pkey);
- myfree((char *)head);
+ myfree((char *) head);
}
}
argv_free(tlsa->certs);
if (tlsa->pkeys)
argv_free(tlsa->pkeys);
- myfree((char *)tlsa);
+ myfree((char *) tlsa);
}
/* tls_dane_free - free a TLS_DANE structure */
-void tls_dane_free(TLS_DANE *dane)
+void tls_dane_free(TLS_DANE *dane)
{
TLS_TLSA *tlsa;
TLS_TLSA *next;
free_ta_certs(dane);
free_ta_pkeys(dane);
- myfree((char *)dane);
+ myfree((char *) dane);
}
/* dane_free - ctable style */
static void dane_free(void *dane, void *unused_context)
{
- tls_dane_free((TLS_DANE *)dane);
+ tls_dane_free((TLS_DANE *) dane);
}
/* tlsa_sort - sort digests for a single certusage */
TLS_DANE *tls_dane_final(TLS_DANE *dane)
{
+
/*
* We only sort the trust anchors, see tls_serverid_digest().
*/
/*
* Correct computation of the session cache serverid requires a TLSA
- * digest list that is sorted by algorithm name. Below we maintain
- * the sort order (by algorithm name canonicalized to lowercase).
+ * digest list that is sorted by algorithm name. Below we maintain the
+ * sort order (by algorithm name canonicalized to lowercase).
*/
for (; *tlsap; tlsap = &(*tlsap)->next) {
int cmp = strcasecmp(mdalg, (*tlsap)->mdalg);
break;
}
- new = (TLS_TLSA *)mymalloc(sizeof(*new));
+ new = (TLS_TLSA *) mymalloc(sizeof(*new));
new->mdalg = lowercase(mystrdup(mdalg));
new->certs = 0;
new->pkeys = 0;
/* tls_dane_split - split and append digests */
-void tls_dane_split(TLS_DANE *dane, int certusage, int selector,
- const char *mdalg, const char *digest, const char *delim)
+void tls_dane_split(TLS_DANE *dane, int certusage, int selector,
+ const char *mdalg, const char *digest, const char *delim)
{
TLS_TLSA **tlsap;
TLS_TLSA *tlsa;
/* dane_add - add a digest entry */
static void dane_add(TLS_DANE *dane, int certusage, int selector,
- const char *mdalg, char *digest)
+ const char *mdalg, char *digest)
{
TLS_TLSA **tlsap;
TLS_TLSA *tlsa;
switch (certusage) {
case DNS_TLSA_USAGE_CA_CONSTRAINT:
case DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION:
- certusage = TLS_DANE_TA; /* Collapse 0/2 -> 2 */
+ certusage = TLS_DANE_TA; /* Collapse 0/2 -> 2 */
break;
case DNS_TLSA_USAGE_SERVICE_CERTIFICATE_CONSTRAINT:
case DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE:
- certusage = TLS_DANE_EE; /* Collapse 1/3 -> 3 */
+ certusage = TLS_DANE_EE; /* Collapse 1/3 -> 3 */
break;
}
uint8_t mtype;
int mlen;
const unsigned char *p;
- X509 *x = 0; /* OpenSSL tries to re-use *x if x!=0 */
- EVP_PKEY *k = 0; /* OpenSSL tries to re-use *k if k!=0 */
+ X509 *x = 0; /* OpenSSL tries to re-use *x if x!=0 */
+ EVP_PKEY *k = 0; /* OpenSSL tries to re-use *k if k!=0 */
if (rr == 0)
msg_panic("null TLSA rr");
- for (/* nop */; rr; rr = rr->next) {
+ for ( /* nop */ ; rr; rr = rr->next) {
const char *mdalg = 0;
int mdlen;
char *digest;
int same = (strcasecmp(rr->rname, rr->qname) == 0);
- uint8_t *ip = (uint8_t *)rr->data;
+ uint8_t *ip = (uint8_t *) rr->data;
#define rcname(rr) (same ? "" : rr->qname)
#define rarrow(rr) (same ? "" : " -> ")
/* Skip malformed */
if ((mlen = rr->data_len - 3) < 0) {
msg_warn("truncated length %u RR: %s%s%s IN TLSA ...",
- (unsigned)rr->data_len, rcname(rr), rarrow(rr), rr->rname);
+ (unsigned) rr->data_len, rcname(rr), rarrow(rr), rr->rname);
continue;
}
-
switch (usage = *ip++) {
default:
msg_warn("unsupported certificate usage %u in RR: "
break;
}
dane_add(dane, usage, selector, mdalg,
- digest = tls_digest_encode((unsigned char *)ip, mdlen));
+ digest = tls_digest_encode((unsigned char *) ip, mdlen));
break;
case DNS_TLSA_MATCHING_TYPE_NO_HASH_USED:
- p = (unsigned char *)ip;
+ p = (unsigned char *) ip;
/* Validate the cert or public key via d2i_mumble() */
switch (selector) {
usage, selector, mtype);
continue;
}
+
/*
* When a full trust-anchor certificate is published via DNS,
* we may need to use it to validate the server trust chain.
- * Store it away for later use. We collapse certificate usage
- * 0/2 because MTAs don't stock a complete list of the usual
- * browser-trusted CAs. Thus, here (and in the public key
- * case below) we treat the usages identically.
+ * Store it away for later use. We collapse certificate
+ * usage 0/2 because MTAs don't stock a complete list of the
+ * usual browser-trusted CAs. Thus, here (and in the public
+ * key case below) we treat the usages identically.
*/
switch (usage) {
case DNS_TLSA_USAGE_CA_CONSTRAINT:
EVP_PKEY_free(k);
break;
}
+
/*
- * The cert or key was valid, just digest the raw object,
- * and encode the digest value. We choose SHA256.
+ * The cert or key was valid, just digest the raw object, and
+ * encode the digest value. We choose SHA256.
*/
dane_add(dane, usage, selector, sha256,
- digest = tls_data_fprint((char *)ip, mlen, sha256));
+ digest = tls_data_fprint((char *) ip, mlen, sha256));
break;
}
if (msg_verbose || dane_verbose)
break;
}
- return ((void *)tls_dane_final(dane));
+ return ((void *) tls_dane_final(dane));
}
/* tls_dane_resolve - cached map: (host, proto, port) -> TLS_DANE */
TLS_DANE *tls_dane_resolve(const char *host, const char *proto,
- unsigned port)
+ unsigned port)
{
static VSTRING *qname;
TLS_DANE *dane;
if (qname == 0)
qname = vstring_alloc(64);
vstring_sprintf(qname, "_%u._%s.%s", ntohs(port), proto, host);
- dane = (TLS_DANE *)ctable_locate(dane_cache, STR(qname));
+ dane = (TLS_DANE *) ctable_locate(dane_cache, STR(qname));
if (timecmp(event_time(), dane->expires) > 0)
- dane = (TLS_DANE *)ctable_refresh(dane_cache, STR(qname));
+ dane = (TLS_DANE *) ctable_refresh(dane_cache, STR(qname));
if (dane->flags & TLS_DANE_FLAG_ERROR)
return (0);
/* tls_dane_load_trustfile - load trust anchor certs or keys from file */
-int tls_dane_load_trustfile(TLS_DANE *dane, const char *tafile)
+int tls_dane_load_trustfile(TLS_DANE *dane, const char *tafile)
{
BIO *bp;
char *name = 0;
unsigned char *data = 0;
long len;
int tacount;
- char *errtype = 0; /* if error: cert or pkey? */
+ char *errtype = 0; /* if error: cert or pkey? */
/* nop */
if (tafile == 0 || *tafile == 0)
return (1);
/*
- * On each call, PEM_read() wraps a stdio file in a BIO_NOCLOSE bio, calls
- * PEM_read_bio() and then frees the bio. It is just as easy to open a
- * BIO as a stdio file, so we use BIOs and call PEM_read_bio() directly.
+ * On each call, PEM_read() wraps a stdio file in a BIO_NOCLOSE bio,
+ * calls PEM_read_bio() and then frees the bio. It is just as easy to
+ * open a BIO as a stdio file, so we use BIOs and call PEM_read_bio()
+ * directly.
*/
if ((bp = BIO_new_file(tafile, "r")) == NULL) {
msg_warn("error opening trust anchor file: %s: %m", tafile);
return (0);
}
-
/* Don't report old news */
ERR_clear_error();
if (cert && (p - data) == len) {
selector = DNS_TLSA_SELECTOR_FULL_CERTIFICATE;
- digest = tls_data_fprint((char *)data, len, sha256);
+ digest = tls_data_fprint((char *) data, len, sha256);
dane_add(dane, usage, selector, sha256, digest);
myfree(digest);
ta_cert_insert(dane, cert);
if (pkey && (p - data) == len) {
selector = DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO;
- digest = tls_data_fprint((char *)data, len, sha256);
+ digest = tls_data_fprint((char *) data, len, sha256);
dane_add(dane, usage, selector, sha256, digest);
myfree(digest);
ta_pkey_insert(dane, pkey);
tafile, errtype);
return (0);
}
-
if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) {
/* Reached end of PEM file */
ERR_clear_error();
return (tacount > 0);
}
-
/* Some other PEM read error */
tls_print_errors();
return (0);
}
/*
- * Allow users to set options not in SSL_OP_ALL, and not already
- * managed via other Postfix parameters.
+ * Allow users to set options not in SSL_OP_ALL, and not already managed
+ * via other Postfix parameters.
*/
if (*var_tls_ssl_options) {
- long enable;
+ long enable;
+
enable = long_name_mask_opt(VAR_TLS_SSL_OPTIONS, ssl_op_tweaks,
var_tls_ssl_options, NAME_MASK_ANY_CASE |
NAME_MASK_NUMBER | NAME_MASK_WARN);
tok822_free(tree->head);
tree->head = 0;
}
- /* XXX must be localpart only, not user@domain form. */
- if (tree->head == 0)
+ /* XXX Re-resolve the surrogate, in case already in user@domain form. */
+ if (tree->head == 0) {
tree->head = tok822_scan(var_empty_addr, &tree->tail);
+ continue;
+ }
+
+ /* XXX Re-resolve with @$myhostname for backwards compatibility. */
+ if (domain == 0 && saved_domain == 0) {
+ tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
+ tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0));
+ continue;
+ }
/*
* We're done. There are no domains left to strip off the address,
* partial lookup keys with regular expressions.
*/
if ((stripped_addr = strip_addr(addr, DISCARD_EXTENSION,
- *var_rcpt_delim)) != 0) {
+ var_rcpt_delim)) != 0) {
found = find_transport_entry(tp, stripped_addr, rcpt_domain, PARTIAL,
channel, nexthop);
/* .IP "\fBresolve_dequoted_address (yes)\fR"
/* Resolve a recipient address safely instead of correctly, by
/* looking inside quotes.
+/* .PP
+/* Available with Postfix version 2.1 and later:
/* .IP "\fBresolve_null_domain (no)\fR"
/* Resolve an address that ends in the "@" null domain as if the
/* local hostname were specified, instead of rejecting the address as
/* invalid.
+/* .PP
+/* Available with Postfix version 2.3 and later:
/* .IP "\fBresolve_numeric_domain (no)\fR"
/* Resolve "user@ipaddress" as "user@[ipaddress]", instead of
/* rejecting the address as invalid.
/* With locally submitted mail, append the string ".$mydomain" to
/* addresses that have no ".domain" information.
/* .IP "\fBrecipient_delimiter (empty)\fR"
-/* The separator between user names and address extensions (user+foo).
+/* The set of characters that can separate a user name from its
+/* address extension (user+foo).
/* .IP "\fBswap_bangpath (yes)\fR"
/* Enable the rewriting of "site!user" into "user@site".
/* .PP
static int argv_cmp(const void *e1, const void *e2)
{
- const char *s1 = *(const char **)e1;
- const char *s2 = *(const char **)e2;
+ const char *s1 = *(const char **) e1;
+ const char *s2 = *(const char **) e2;
+
return strcmp(s1, s2);
}
VSTRING *buf;
char *member;
char *val;
+ const char *old;
int old_lineno;
int lineno;
const char *err;
err, STR(buf));
if (msg_verbose > 1)
msg_info("%s: %s = %s", myname, member, val);
+ if ((old = dict->lookup(dict, member)) != 0
+ && strcmp(old, val) != 0)
+ msg_warn("%s, line %d: overriding earlier entry: %s=%s",
+ VSTREAM_PATH(fp), lineno, member, old);
if (dict->update(dict, member, val) != 0)
msg_fatal("%s, line %d: unable to update %s:%s",
VSTREAM_PATH(fp), lineno, dict->type, dict->name);
/* int true_res;
/* int false_res;
/* DESCRIPTION
-/* The functions in this module are macros that provide a
-/* convenient interface to poll_fd().
+/* The read*() and write*() functions in this module are macros
+/* that provide a convenient interface to poll_fd().
/*
/* readable() asks the kernel if the specified file descriptor
/* is readable, i.e. a read operation would not block.
/* it is false. They never return an error indication.
/*
/* read_wait() and write_wait() return zero when the requested
-/* POLL_FD_READ or POLL_FD_WRITE condition is true, -1 with
-/* errno set to ETIMEDOUT when it is false.
+/* POLL_FD_READ or POLL_FD_WRITE condition is true, -1 (with
+/* errno set to ETIMEDOUT) when it is false.
/*
/* poll_fd() returns true_res when the requested POLL_FD_READ
/* or POLL_FD_WRITE condition is true, false_res when it is