-TPOSTMAP_KEY_STATE
-TPOST_MAIL_STATE
-TPRIVATE_STR_TABLE
--TPS_DNSBL_ENTRY
--TPS_DNS_STREAM
+-TPS_DNSBL_SITE
+-TPS_DNSBL_SCORE
-TPS_STATE
-TQMGR_ENTRY
-TQMGR_FEEDBACK
"smtp_dns_resolver_options = res_defnames" to get the old
behavior, which can produce unexpected results. Files:
smtp/smtp.c, smtp/smtp_params.c, smtp/smtp_addr.c.
+
+20100828
+
+ Refactoring: postscreen source code broken up into multiple
+ files, and identifiers updated to match changes in their
+ purpose. This will be the baseline for adding support for
+ DNSBL weighting, then a dummy engine to collect forensic
+ evidence with the option of future protocol checks. Files:
+ postscreen/*.[hc], Makefile.in.
+
+20100829
+
+ Postscreen DNSBL support for optional fixed-string filters
+ and optional integral weight factors (use negative weights
+ for whitelisting). See RELEASE_NOTES and postconf(5) for
+ details. Files: postscreen/postscreen_dnsbl.c,
+ proto/postconf.proto, mantools.postlink, global/mail_params.h.
+
+ Incompatibility: the postscreen-to-dnsblog protocol was
+ changed to support DNSBL query result filters. Use "postfix
+ reload" after installing the new version otherwise the
+ dnsblog(8) server may complain.
+
+20100830
+
+ Polished the postscreen documentation and comments to clarify
+ the user interface and implementation. No code changes.
If you upgrade from Postfix 2.6 or earlier, read RELEASE_NOTES-2.7
before proceeding.
+Incompatibility with snapshot 20100830
+======================================
+
+Use "postfix reload" after installing this code, otherwise the
+dnsblog(8) daemon may complain. The postscreen-to-dnsblog protocol
+had to be changed to support DNSBL query result filters.
+
+Major changes with snapshot 20100830
+====================================
+
+Postscreen DNSBL support is extended with optional fixed-string
+filters, with optional integral weight factors, and with an adjustable
+threshold to block SMTP clients with DNSBL score >= that threshold.
+Support for wild-card patterns will be added later.
+
+The updated postscreen configuration syntax is:
+
+ postscreen_dnsbl_sites = domain[=ipaddr][*weight] ...
+ postscreen_dnsbl_threshold = score
+
+Elements inside [] are optional, ipaddr is an IPv4 address, and
+weight and score are integral numbers. The [] are not part of the
+postscreen_dnsbl_sites input. By default, weight and score are
+equal to 1, and entries without filter will match any non-error
+DNSBL reply. Use a negative weight value for whitelisting.
+
+Examples:
+
+To use example.com as a high-confidence blocklist, and to block
+mail with example.net and example.org only when both agree, use:
+
+ postscreen_dnsbl_threshold = 2
+ postscreen_dnsbl_sites = example.com*2, example.net, example.org
+
+To filter only DNSBL replies containing 127.0.0.4, use:
+
+ postscreen_dnsbl_sites = example.com=127.0.0.4
+
+See also postconf(5) for the fine details.
+
Incompatibility with snapshot 20100827
======================================
<dd> Continue waiting until the <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> time has
elapsed, and report whether the client triggers a PREGREET or HANGUP
-error, or whether the client is listed at the DNSBL sites specified
-with the <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> parameter. Take the corresponding
+error, or whether the client's combined DNSBL score is equal to or
+greater than a threshold (as specified with the <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a>
+and <a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a> parameters). Take the corresponding
action, or forward the connection to a real SMTP server process.
</dd>
</DD>
<DT><b><a name="postscreen_cache_map">postscreen_cache_map</a>
-(default: btree:$<a href="postconf.5.html#data_directory">data_directory</a>/ps_whitelist)</b></DT><DD>
+(default: btree:$<a href="postconf.5.html#data_directory">data_directory</a>/ps_cache)</b></DT><DD>
<p> Persistent storage for the <a href="postscreen.8.html">postscreen(8)</a> server decisions. </p>
<DT><b><a name="postscreen_dnsbl_action">postscreen_dnsbl_action</a>
(default: continue)</b></DT><DD>
-<p>The action that <a href="postscreen.8.html">postscreen(8)</a> takes when an SMTP client is listed
-at the DNS blocklist domains specified with the <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a>
-parameter. Specify one of the following: </p>
+<p>The action that <a href="postscreen.8.html">postscreen(8)</a> takes when an SMTP client's combined
+DNSBL score is equal to or greater than a threshold (as defined
+with the <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> and <a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a>
+parameters). Specify one of the following: </p>
<dl>
<DT><b><a name="postscreen_dnsbl_sites">postscreen_dnsbl_sites</a>
(default: empty)</b></DT><DD>
-<p>Optional list of DNS blocklist domains. When the list is non-enpty,
-the <a href="dnsblog.8.html">dnsblog(8)</a> daemon will query these domains with the IP addresses
-of non-whitelisted <a href="postscreen.8.html">postscreen(8)</a> clients. Specify a list of domain
-names, separated by comma or whitespace. </p>
+<p>Optional list of DNS blocklist domains, filters and weight
+factors. When the list is non-empty, the <a href="dnsblog.8.html">dnsblog(8)</a> daemon will
+query these domains with the IP addresses of non-whitelisted remote
+SMTP clients, and <a href="postscreen.8.html">postscreen(8)</a> will update an SMTP client's DNSBL
+score with each non-error reply. </p>
+
+<p> When a client's score is equal to or greater than the threshold
+specified with <a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a>, <a href="postscreen.8.html">postscreen(8)</a> can drop
+the connection with the SMTP client. </p>
+
+<p> Specify a list of domain=filter*weight entries, separated by
+comma or whitespace. </p>
+
+<ul>
+
+<li> <p> When no "=filter" is specified, <a href="postscreen.8.html">postscreen(8)</a> will use any
+non-error DNSBL reply. Otherwise, the filter must be an IPv4
+address, and <a href="postscreen.8.html">postscreen(8)</a> uses only DNSBL replies that match the
+filter. </p>
+
+<li> <p> When no "*weight" is specified, <a href="postscreen.8.html">postscreen(8)</a> increments
+the SMTP client's DNSBL score by 1. Otherwise, the weight must be
+an integral number, and <a href="postscreen.8.html">postscreen(8)</a> adds the specified weight to
+the SMTP client's DNSBL score. Specify a negative number for
+whitelisting. </p>
+
+<li> <p> When one <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> entry produces multiple
+DNSBL responses, <a href="postscreen.8.html">postscreen(8)</a> applies the weight at most once.
+</p>
+
+</ul>
+
+<p> Examples: </p>
+
+<p> To use example.com as a high-confidence blocklist, and to
+block mail with example.net and example.org only when both agree:
+</p>
+
+<pre>
+<a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a> = 2
+<a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> = example.com*2, example.net, example.org
+</pre>
+
+<p> To filter only DNSBL replies containing 127.0.0.4: </p>
+
+<pre>
+<a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> = example.com=127.0.0.4
+</pre>
<p> This feature is available in Postfix 2.8. </p>
+</DD>
+
+<DT><b><a name="postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a>
+(default: 1)</b></DT><DD>
+
+<p> The inclusive lower bound for blocking an SMTP client, 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> This feature is available in Postfix 2.8. </p>
+
+
</DD>
<DT><b><a name="postscreen_greet_action">postscreen_greet_action</a>
<dt> continue </dt>
<dd> Continue waiting until the <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> time has
-elapsed. If the client is listed at the DNS blocklist domains
-specified with the <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> parameter, execute the
-action specified with the <a href="postconf.5.html#postscreen_dnsbl_action">postscreen_dnsbl_action</a> parameter, otherwise
-forward the connection to a real SMTP server process. </dd>
+elapsed. If the client's combined DNSBL score is equal to or greater
+than a threshold (as specified with the <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> and
+<a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a> parameters), execute the action specified
+with the <a href="postconf.5.html#postscreen_dnsbl_action">postscreen_dnsbl_action</a> parameter, otherwise forward the
+connection to a real SMTP server process. </dd>
<dt> drop </dt>
<dt> continue </dt>
<dd> Continue waiting until the <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> time has
-elapsed, and report whether the client is listed at the DNSBL sites
-specified with the <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> parameter. Do not
-forward the broken connection to a real SMTP server process. </dd>
+elapsed, and report whether the client's combined DNSBL score is
+equal to or greater than a threshold (as defined with the
+<a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> and <a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a> parameters).
+Do not forward the broken connection to a real SMTP server process.
+</dd>
<dt> drop </dt>
(default: empty)</b></DT><DD>
<p> DNS Resolver options for the Postfix SMTP client. Specify zero
-or more of the following, separated by comma or whitespace. Option
-names are case-sensitive. Some options refer to domain names that
-are specified in /etc/resolv.conf or equivalent. </p>
+or more of the following options, separated by comma or whitespace.
+Option names are case-sensitive. Some options refer to domain names
+that are specified in the file /etc/resolv.conf or equivalent. </p>
<dl>
<dt><b>res_defnames</b></dt>
-<dd> Append the <a href="ADDRESS_CLASS_README.html#default_domain_class">default domain</a> name to single-component names (those
-that do not contain a dot). This can produce incorrect results,
-and was the behavior prior to Postfix 2.8. </dd>
+<dd> Append the current domain name to single-component names (those
+that do not contain a "." character). This can produce incorrect
+results, and is the hard-coded behavior prior to Postfix 2.8. </dd>
<dt><b>res_dnsrch</b></dt>
are made in parallel.
When the <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> time has elapsed, and the
- SMTP client address is listed with at least one of these
- blocklists, this is logged as:
+ combined DNSBL score is equal to or greater than the
+ <a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a> parameter value, this is logged
+ as:
<b>DNSBL rank</b> <i>count</i> <b>for</b> <i>address</i>
- Translation: the client at <i>address</i> is listed with <i>count</i>
- DNSBL servers. The <i>count</i> does not depend on the number of
- DNS records that an individual DNSBL server returns.
+ Translation: the SMTP client at <i>address</i> has a combined
+ DNSBL score of <i>count</i>.
The <a href="postconf.5.html#postscreen_dnsbl_action">postscreen_dnsbl_action</a> parameter specifies the action
- that is taken next:
+ that is taken when the combined DNSBL score is equal to or
+ greater than the threshold:
<b>continue</b> (default)
Forward the connection to a real SMTP server
process.
- <b>drop</b> Drop the connection immediately with a 521 SMTP
- reply. In a future implementation, the connection
- may instead be passed to a dummy SMTP protocol
- engine that logs sender and recipient information.
+ <b>drop</b> Drop the connection immediately with a 521 SMTP
+ reply. In a future implementation, the connection
+ may instead be passed to a dummy SMTP protocol
+ engine that logs sender and recipient information.
<b>SECURITY</b>
The <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server is moderately security-sensitive.
- It talks to untrusted clients on the network. The process
+ It talks to untrusted clients on the network. The process
can be run chrooted at fixed low privilege.
<b>STANDARDS</b>
Problems and transactions are logged to <b>syslogd</b>(8).
<b>CONFIGURATION PARAMETERS</b>
- Changes to <a href="postconf.5.html">main.cf</a> are not picked up automatically, as
- <a href="postscreen.8.html"><b>postscreen</b>(8)</a> processes may run for several hours. Use
+ Changes to <a href="postconf.5.html">main.cf</a> are not picked up automatically, as
+ <a href="postscreen.8.html"><b>postscreen</b>(8)</a> processes may run for several hours. Use
the command "postfix reload" after a configuration change.
- The text below provides only a parameter summary. See
+ The text below provides only a parameter summary. See
<a href="postconf.5.html"><b>postconf</b>(5)</a> for more details including examples.
<b>TRIAGE PARAMETERS</b>
<b><a href="postconf.5.html#postscreen_blacklist_action">postscreen_blacklist_action</a> (continue)</b>
- The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when an SMTP
- client is permanently blacklisted with the
+ The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when an SMTP
+ client is permanently blacklisted with the
<a href="postconf.5.html#postscreen_blacklist_networks">postscreen_blacklist_networks</a> parameter.
<b><a href="postconf.5.html#postscreen_blacklist_networks">postscreen_blacklist_networks</a> (empty)</b>
Network addresses that are permanently blacklisted;
- see the <a href="postconf.5.html#postscreen_blacklist_action">postscreen_blacklist_action</a> parameter for
+ see the <a href="postconf.5.html#postscreen_blacklist_action">postscreen_blacklist_action</a> parameter for
possible actions.
<b><a href="postconf.5.html#postscreen_dnsbl_action">postscreen_dnsbl_action</a> (continue)</b>
- The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when an SMTP
- client is listed at the DNS blocklist domains spec-
- ified with the <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> parameter.
+ The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when an SMTP
+ client's combined DNSBL score is equal to or
+ greater than a threshold (as defined with the
+ <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> and postscreen_dnsbl_thresh-
+ old parameters).
<b><a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> (empty)</b>
- Optional list of DNS blocklist domains.
+ Optional list of DNS blocklist domains, filters and
+ weight factors.
+
+ <b><a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a> (1)</b>
+ The inclusive lower bound for blocking an SMTP
+ client, based on its combined DNSBL score as
+ defined with the <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> parameter.
<b><a href="postconf.5.html#postscreen_greet_action">postscreen_greet_action</a> (continue)</b>
- The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when an SMTP
+ The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when an SMTP
client speaks before its turn within the time spec-
ified with the <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> parameter.
The <i>text</i> in the optional "220-<i>text</i>..." server
response that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> sends ahead of the real
Postfix SMTP server's "220 text..." response, in an
- attempt to confuse bad SMTP clients so that they
+ attempt to confuse bad SMTP clients so that they
speak before their turn (pre-greet).
<b><a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> (4s)</b>
The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will wait for
- an SMTP client to send a command before its turn,
+ an SMTP client to send a command before its turn,
and for DNS blocklist lookup results to arrive.
<b><a href="postconf.5.html#postscreen_hangup_action">postscreen_hangup_action</a> (continue)</b>
- The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when an SMTP
+ The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when an SMTP
client disconnects without sending data, within the
- time specified with the <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a>
+ time specified with the <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a>
parameter.
<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 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 SMTP server process.
<b><a href="postconf.5.html#postscreen_whitelist_networks">postscreen_whitelist_networks</a> ($<a href="postconf.5.html#mynetworks">mynetworks</a>)</b>
Network addresses that are permanently whitelisted,
- and that will not be subjected to <a href="postscreen.8.html"><b>postscreen</b>(8)</a>
+ and that will not be subjected to <a href="postscreen.8.html"><b>postscreen</b>(8)</a>
checks.
<b><a href="postconf.5.html#smtpd_service">smtpd_service</a> (smtpd)</b>
- The internal service that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> forwards
+ The internal service that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> forwards
allowed connections to.
<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:$<a href="postconf.5.html#data_directory">data_directory</a>/ps_whitelist)</b>
- Persistent storage for the <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server
+ <b><a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a> (btree:$<a href="postconf.5.html#data_directory">data_directory</a>/ps_cache)</b>
+ 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> (1d)</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_cache_ttl">postscreen_cache_ttl</a> (1d)</b>
- The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will cache a
+ The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will cache a
decision for a specific SMTP client IP address.
<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#daemon_timeout">daemon_timeout</a> (18000s)</b>
- How much time a Postfix daemon process may take to
- handle a request before it is terminated by a
+ How much time a Postfix daemon process may take to
+ handle a request before it is terminated by a
built-in watchdog timer.
<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#ipc_timeout">ipc_timeout</a> (3600s)</b>
over an internal communication channel.
<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>
syslogd(8), system logging
<b>LICENSE</b>
- The Secure Mailer license must be distributed with this
+ The Secure Mailer license must be distributed with this
software.
<b>AUTHOR(S)</b>
.IP "continue"
Continue waiting until the postscreen_greet_wait time has
elapsed, and report whether the client triggers a PREGREET or HANGUP
-error, or whether the client is listed at the DNSBL sites specified
-with the postscreen_dnsbl_sites parameter. Take the corresponding
+error, or whether the client's combined DNSBL score is equal to or
+greater than a threshold (as specified with the postscreen_dnsbl_sites
+and postscreen_dnsbl_threshold parameters). Take the corresponding
action, or forward the connection to a real SMTP server process.
.IP "drop"
Drop the connection immediately with a 521 SMTP reply, without
(weeks).
.PP
This feature is available in Postfix 2.8.
-.SH postscreen_cache_map (default: btree:$data_directory/ps_whitelist)
+.SH postscreen_cache_map (default: btree:$data_directory/ps_cache)
Persistent storage for the \fBpostscreen\fR(8) server decisions.
.PP
This feature is available in Postfix 2.8.
.PP
This feature is available in Postfix 2.8.
.SH postscreen_dnsbl_action (default: continue)
-The action that \fBpostscreen\fR(8) takes when an SMTP client is listed
-at the DNS blocklist domains specified with the postscreen_dnsbl_sites
-parameter. Specify one of the following:
+The action that \fBpostscreen\fR(8) takes when an SMTP client's combined
+DNSBL score is equal to or greater than a threshold (as defined
+with the postscreen_dnsbl_sites and postscreen_dnsbl_threshold
+parameters). Specify one of the following:
.IP "continue"
Forward the connection to a real SMTP server process.
.IP "drop"
.PP
This feature is available in Postfix 2.8.
.SH postscreen_dnsbl_sites (default: empty)
-Optional list of DNS blocklist domains. When the list is non-enpty,
-the \fBdnsblog\fR(8) daemon will query these domains with the IP addresses
-of non-whitelisted \fBpostscreen\fR(8) clients. Specify a list of domain
-names, separated by comma or whitespace.
+Optional list of DNS blocklist domains, filters and weight
+factors. When the list is non-empty, the \fBdnsblog\fR(8) daemon will
+query these domains with the IP addresses of non-whitelisted remote
+SMTP clients, and \fBpostscreen\fR(8) will update an SMTP client's DNSBL
+score with each non-error reply.
+.PP
+When a client's score is equal to or greater than the threshold
+specified with postscreen_dnsbl_threshold, \fBpostscreen\fR(8) can drop
+the connection with the SMTP client.
+.PP
+Specify a list of domain=filter*weight entries, separated by
+comma or whitespace.
+.IP \(bu
+When no "=filter" is specified, \fBpostscreen\fR(8) will use any
+non-error DNSBL reply. Otherwise, the filter must be an IPv4
+address, and \fBpostscreen\fR(8) uses only DNSBL replies that match the
+filter.
+.IP \(bu
+When no "*weight" is specified, \fBpostscreen\fR(8) increments
+the SMTP client's DNSBL score by 1. Otherwise, the weight must be
+an integral number, and \fBpostscreen\fR(8) adds the specified weight to
+the SMTP client's DNSBL score. Specify a negative number for
+whitelisting.
+.IP \(bu
+When one postscreen_dnsbl_sites entry produces multiple
+DNSBL responses, \fBpostscreen\fR(8) applies the weight at most once.
+.PP
+Examples:
+.PP
+To use example.com as a high-confidence blocklist, and to
+block mail with example.net and example.org only when both agree:
+.PP
+.nf
+.na
+.ft C
+postscreen_dnsbl_threshold = 2
+postscreen_dnsbl_sites = example.com*2, example.net, example.org
+.fi
+.ad
+.ft R
+.PP
+To filter only DNSBL replies containing 127.0.0.4:
+.PP
+.nf
+.na
+.ft C
+postscreen_dnsbl_sites = example.com=127.0.0.4
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_dnsbl_threshold (default: 1)
+The inclusive lower bound for blocking an SMTP client, based on
+its combined DNSBL score as defined with the postscreen_dnsbl_sites
+parameter.
.PP
This feature is available in Postfix 2.8.
.SH postscreen_greet_action (default: continue)
parameter. Specify one of the following:
.IP "continue"
Continue waiting until the postscreen_greet_wait time has
-elapsed. If the client is listed at the DNS blocklist domains
-specified with the postscreen_dnsbl_sites parameter, execute the
-action specified with the postscreen_dnsbl_action parameter, otherwise
-forward the connection to a real SMTP server process.
+elapsed. If the client's combined DNSBL score is equal to or greater
+than a threshold (as specified with the postscreen_dnsbl_sites and
+postscreen_dnsbl_threshold parameters), execute the action specified
+with the postscreen_dnsbl_action parameter, otherwise forward the
+connection to a real SMTP server process.
.IP "drop"
Drop the connection immediately with a 521 SMTP reply, without
examining DNSBL lookup results.
postscreen_greet_wait parameter. Specify one of the following:
.IP "continue"
Continue waiting until the postscreen_greet_wait time has
-elapsed, and report whether the client is listed at the DNSBL sites
-specified with the postscreen_dnsbl_sites parameter. Do not
-forward the broken connection to a real SMTP server process.
+elapsed, and report whether the client's combined DNSBL score is
+equal to or greater than a threshold (as defined with the
+postscreen_dnsbl_sites and postscreen_dnsbl_threshold parameters).
+Do not forward the broken connection to a real SMTP server process.
.IP "drop"
Drop the connection immediately, without reporting DNSBL lookup
results.
discard EHLO keywords selectively.
.SH smtp_dns_resolver_options (default: empty)
DNS Resolver options for the Postfix SMTP client. Specify zero
-or more of the following, separated by comma or whitespace. Option
-names are case-sensitive. Some options refer to domain names that
-are specified in /etc/resolv.conf or equivalent.
+or more of the following options, separated by comma or whitespace.
+Option names are case-sensitive. Some options refer to domain names
+that are specified in the file /etc/resolv.conf or equivalent.
.IP "\fBres_defnames\fR"
-Append the default domain name to single-component names (those
-that do not contain a dot). This can produce incorrect results,
-and was the behavior prior to Postfix 2.8.
+Append the current domain name to single-component names (those
+that do not contain a "." character). This can produce incorrect
+results, and is the hard-coded behavior prior to Postfix 2.8.
.IP "\fBres_dnsrch\fR"
Search for host names in the current domain and in parent
domains. This can produce incorrect results and is therefore not
are made in parallel.
When the postscreen_greet_wait time has elapsed, and the
-SMTP client address is listed with at least one of these
-blocklists, this is logged as:
+combined DNSBL score is equal to or greater than the
+postscreen_dnsbl_threshold parameter value, this is logged
+as:
.sp
.nf
\fBDNSBL rank \fIcount \fBfor \fIaddress\fR
.fi
.sp
-Translation: the client at \fIaddress\fR is listed with
-\fIcount\fR DNSBL servers. The \fIcount\fR does not
-depend on the number of DNS records that an individual DNSBL
-server returns.
+Translation: the SMTP client at \fIaddress\fR has a combined
+DNSBL score of \fIcount\fR.
The postscreen_dnsbl_action parameter specifies the action
-that is taken next:
+that is taken when the combined DNSBL score is equal to or
+greater than the threshold:
.IP "\fBcontinue\fR (default)"
Forward the connection to a real SMTP server process.
.IP \fBdrop\fR
Network addresses that are permanently blacklisted; see the
postscreen_blacklist_action parameter for possible actions.
.IP "\fBpostscreen_dnsbl_action (continue)\fR"
-The action that \fBpostscreen\fR(8) takes when an SMTP client is listed
-at the DNS blocklist domains specified with the postscreen_dnsbl_sites
-parameter.
+The action that \fBpostscreen\fR(8) takes when an SMTP client's combined
+DNSBL score is equal to or greater than a threshold (as defined
+with the postscreen_dnsbl_sites and postscreen_dnsbl_threshold
+parameters).
.IP "\fBpostscreen_dnsbl_sites (empty)\fR"
-Optional list of DNS blocklist domains.
+Optional list of DNS blocklist domains, filters and weight
+factors.
+.IP "\fBpostscreen_dnsbl_threshold (1)\fR"
+The inclusive lower bound for blocking an SMTP client, based on
+its combined DNSBL score as defined with the postscreen_dnsbl_sites
+parameter.
.IP "\fBpostscreen_greet_action (continue)\fR"
The action that \fBpostscreen\fR(8) takes when an SMTP client speaks
before its turn within the time specified with the postscreen_greet_wait
.fi
.IP "\fBpostscreen_cache_cleanup_interval (12h)\fR"
The amount of time between \fBpostscreen\fR(8) cache cleanup runs.
-.IP "\fBpostscreen_cache_map (btree:$data_directory/ps_whitelist)\fR"
+.IP "\fBpostscreen_cache_map (btree:$data_directory/ps_cache)\fR"
Persistent storage for the \fBpostscreen\fR(8) server decisions.
.IP "\fBpostscreen_cache_retention_time (1d)\fR"
The amount of time that \fBpostscreen\fR(8) will cache an expired
s;\bpostscreen_greet_wait\b;<a href="postconf.5.html#postscreen_greet_wait">$&</a>;g;
s;\bpostscreen_greet_action\b;<a href="postconf.5.html#postscreen_greet_action">$&</a>;g;
s;\bpostscreen_dnsbl_sites\b;<a href="postconf.5.html#postscreen_dnsbl_sites">$&</a>;g;
+ s;\bpostscreen_dnsbl_threshold\b;<a href="postconf.5.html#postscreen_dnsbl_threshold">$&</a>;g;
s;\bpostscreen_dnsbl_action\b;<a href="postconf.5.html#postscreen_dnsbl_action">$&</a>;g;
s;\bpostscreen_hangup_action\b;<a href="postconf.5.html#postscreen_hangup_action">$&</a>;g;
s;\bpostscreen_whitelist_networks\b;<a href="postconf.5.html#postscreen_whitelist_networks">$&</a>;g;
<p> This feature is available in Postfix 2.7, and as an optional
patch for Postfix 2.6. </p>
-%PARAM postscreen_cache_map btree:$data_directory/ps_whitelist
+%PARAM postscreen_cache_map btree:$data_directory/ps_cache
<p> Persistent storage for the postscreen(8) server decisions. </p>
%PARAM postscreen_dnsbl_sites
-<p>Optional list of DNS blocklist domains. When the list is non-enpty,
-the dnsblog(8) daemon will query these domains with the IP addresses
-of non-whitelisted postscreen(8) clients. Specify a list of domain
-names, separated by comma or whitespace. </p>
+<p>Optional list of DNS blocklist domains, filters and weight
+factors. When the list is non-empty, the dnsblog(8) daemon will
+query these domains with the IP addresses of non-whitelisted remote
+SMTP clients, and postscreen(8) will update an SMTP client's DNSBL
+score with each non-error reply. </p>
+
+<p> When a client's score is equal to or greater than the threshold
+specified with postscreen_dnsbl_threshold, postscreen(8) can drop
+the connection with the SMTP client. </p>
+
+<p> Specify a list of domain=filter*weight entries, separated by
+comma or whitespace. </p>
+
+<ul>
+
+<li> <p> When no "=filter" is specified, postscreen(8) will use any
+non-error DNSBL reply. Otherwise, the filter must be an IPv4
+address, and postscreen(8) uses only DNSBL replies that match the
+filter. </p>
+
+<li> <p> When no "*weight" is specified, postscreen(8) increments
+the SMTP client's DNSBL score by 1. Otherwise, the weight must be
+an integral number, and postscreen(8) adds the specified weight to
+the SMTP client's DNSBL score. Specify a negative number for
+whitelisting. </p>
+
+<li> <p> When one postscreen_dnsbl_sites entry produces multiple
+DNSBL responses, postscreen(8) applies the weight at most once.
+</p>
+
+</ul>
+
+<p> Examples: </p>
+
+<p> To use example.com as a high-confidence blocklist, and to
+block mail with example.net and example.org only when both agree:
+</p>
+
+<pre>
+postscreen_dnsbl_threshold = 2
+postscreen_dnsbl_sites = example.com*2, example.net, example.org
+</pre>
+
+<p> To filter only DNSBL replies containing 127.0.0.4: </p>
+
+<pre>
+postscreen_dnsbl_sites = example.com=127.0.0.4
+</pre>
<p> This feature is available in Postfix 2.8. </p>
%PARAM postscreen_dnsbl_action continue
-<p>The action that postscreen(8) takes when an SMTP client is listed
-at the DNS blocklist domains specified with the postscreen_dnsbl_sites
-parameter. Specify one of the following: </p>
+<p>The action that postscreen(8) takes when an SMTP client's combined
+DNSBL score is equal to or greater than a threshold (as defined
+with the postscreen_dnsbl_sites and postscreen_dnsbl_threshold
+parameters). Specify one of the following: </p>
<dl>
<dt> continue </dt>
<dd> Continue waiting until the postscreen_greet_wait time has
-elapsed. If the client is listed at the DNS blocklist domains
-specified with the postscreen_dnsbl_sites parameter, execute the
-action specified with the postscreen_dnsbl_action parameter, otherwise
-forward the connection to a real SMTP server process. </dd>
+elapsed. If the client's combined DNSBL score is equal to or greater
+than a threshold (as specified with the postscreen_dnsbl_sites and
+postscreen_dnsbl_threshold parameters), execute the action specified
+with the postscreen_dnsbl_action parameter, otherwise forward the
+connection to a real SMTP server process. </dd>
<dt> drop </dt>
<dt> continue </dt>
<dd> Continue waiting until the postscreen_greet_wait time has
-elapsed, and report whether the client is listed at the DNSBL sites
-specified with the postscreen_dnsbl_sites parameter. Do not
-forward the broken connection to a real SMTP server process. </dd>
+elapsed, and report whether the client's combined DNSBL score is
+equal to or greater than a threshold (as defined with the
+postscreen_dnsbl_sites and postscreen_dnsbl_threshold parameters).
+Do not forward the broken connection to a real SMTP server process.
+</dd>
<dt> drop </dt>
<dd> Continue waiting until the postscreen_greet_wait time has
elapsed, and report whether the client triggers a PREGREET or HANGUP
-error, or whether the client is listed at the DNSBL sites specified
-with the postscreen_dnsbl_sites parameter. Take the corresponding
+error, or whether the client's combined DNSBL score is equal to or
+greater than a threshold (as specified with the postscreen_dnsbl_sites
+and postscreen_dnsbl_threshold parameters). Take the corresponding
action, or forward the connection to a real SMTP server process.
</dd>
%PARAM smtp_dns_resolver_options
<p> DNS Resolver options for the Postfix SMTP client. Specify zero
-or more of the following, separated by comma or whitespace. Option
-names are case-sensitive. Some options refer to domain names that
-are specified in /etc/resolv.conf or equivalent. </p>
+or more of the following options, separated by comma or whitespace.
+Option names are case-sensitive. Some options refer to domain names
+that are specified in the file /etc/resolv.conf or equivalent. </p>
<dl>
<dt><b>res_defnames</b></dt>
-<dd> Append the default domain name to single-component names (those
-that do not contain a dot). This can produce incorrect results,
-and was the behavior prior to Postfix 2.8. </dd>
+<dd> Append the current domain name to single-component names (those
+that do not contain a "." character). This can produce incorrect
+results, and is the hard-coded behavior prior to Postfix 2.8. </dd>
<dt><b>res_dnsrch</b></dt>
<p> This feature is available in Postfix 2.8 and later. </p>
+%PARAM postscreen_dnsbl_threshold 1
+
+<p> The inclusive lower bound for blocking an SMTP client, based on
+its combined DNSBL score as defined with the postscreen_dnsbl_sites
+parameter. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
argv_free(types_argv);
name = argv[2];
msg_verbose = 1;
- switch (dns_lookup_v(name, RES_DEFNAMES | RES_DEBUG, &rr, fqdn, why,
+ switch (dns_lookup_v(name, RES_DEBUG, &rr, fqdn, why,
DNS_REQ_FLAG_NONE, types)) {
default:
msg_fatal("%s", vstring_str(why));
static VSTRING *addr;
static VSTRING *query;
static VSTRING *why;
+static VSTRING *result;
/*
* Silly little macros.
*/
#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
/* static void dnsblog_query - query DNSBL for client address */
-static int dnsblog_query(const char *dnsbl_domain, const char *addr)
+static VSTRING *dnsblog_query(VSTRING *result, const char *dnsbl_domain,
+ const char *addr)
{
const char *myname = "dnsblog_query";
ARGV *octets;
DNS_RR *addr_list;
DNS_RR *rr;
MAI_HOSTADDR_STR hostaddr;
- int found = 0;
if (msg_verbose)
msg_info("%s: addr %s dnsbl_domain %s",
}
/*
- * Tack on the RBL domain name and query the DNS for an A record. Don't
- * do this for AAAA records. Yet.
+ * Tack on the RBL domain name and query the DNS for an A record.
*/
vstring_strcat(query, dnsbl_domain);
dns_status = dns_lookup(STR(query), T_A, 0, &addr_list, (VSTRING *) 0, why);
+ VSTRING_RESET(result);
if (dns_status == DNS_OK) {
for (rr = addr_list; rr != 0; rr = rr->next) {
if (dns_rr_to_pa(rr, &hostaddr) == 0) {
} else {
msg_info("addr %s blocked by domain %s as %s",
addr, dnsbl_domain, hostaddr.buf);
- found = 1;
+ if (LEN(result) > 0)
+ vstring_strcat(result, " ");
+ vstring_strcat(result, hostaddr.buf);
}
}
dns_rr_free(addr_list);
msg_warn("%s: lookup error for DNS query %s: %s",
myname, STR(query), STR(why));
}
- return (found);
+ VSTRING_TERMINATE(result);
+ return (result);
}
/* dnsblog_service - perform service for client */
static void dnsblog_service(VSTREAM *client_stream, char *unused_service,
char **argv)
{
- int found;
/*
* Sanity check. This service takes no command-line arguments.
if (attr_scan(client_stream,
ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
ATTR_TYPE_STR, MAIL_ATTR_RBL_DOMAIN, rbl_domain,
- ATTR_TYPE_STR, MAIL_ATTR_ADDR, addr,
+ ATTR_TYPE_STR, MAIL_ATTR_ACT_CLIENT_ADDR, addr,
ATTR_TYPE_END) == 2) {
- found = dnsblog_query(STR(rbl_domain), STR(addr));
+ (void) dnsblog_query(result, STR(rbl_domain), STR(addr));
attr_print(client_stream, ATTR_FLAG_NONE,
ATTR_TYPE_STR, MAIL_ATTR_RBL_DOMAIN, STR(rbl_domain),
- ATTR_TYPE_STR, MAIL_ATTR_ADDR, STR(addr),
- ATTR_TYPE_INT, MAIL_ATTR_STATUS, found,
+ ATTR_TYPE_STR, MAIL_ATTR_ACT_CLIENT_ADDR, STR(addr),
+ ATTR_TYPE_STR, MAIL_ATTR_RBL_ADDR, STR(result),
ATTR_TYPE_END);
vstream_fflush(client_stream);
}
addr = vstring_alloc(100);
query = vstring_alloc(100);
why = vstring_alloc(100);
+ result = vstring_alloc(100);
}
MAIL_VERSION_STAMP_DECLARE;
#define DEF_PS_DNSBL_SITES ""
extern char *var_ps_dnsbl_sites;
+#define VAR_PS_DNSBL_THRESH "postscreen_dnsbl_threshold"
+#define DEF_PS_DNSBL_THRESH 1
+extern int var_ps_dnsbl_thresh;
+
#define VAR_PS_DNSBL_ACTION "postscreen_dnsbl_action"
#define DEF_PS_DNSBL_ACTION "continue"
extern char *var_ps_dnsbl_action;
#define MAIL_ATTR_RBL_TXT "rbl_txt" /* LaMont compatibility */
#define MAIL_ATTR_RBL_CLASS "rbl_class"
#define MAIL_ATTR_RBL_CODE "rbl_code"
+#define MAIL_ATTR_RBL_ADDR "rbl_addr"
/*
* The following attribute names are stored in queue files. Changing this
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20100827"
+#define MAIL_RELEASE_DATE "20100829"
#define MAIL_VERSION_NUMBER "2.8"
#ifdef SNAPSHOT
SHELL = /bin/sh
-SRCS = postscreen.c
-OBJS = postscreen.o
+SRCS = postscreen.c postscreen_dict.c postscreen_dnsbl.c
+OBJS = postscreen.o postscreen_dict.o postscreen_dnsbl.o
HDRS =
TESTSRC =
DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
postscreen.o: ../../include/vstream.h
postscreen.o: ../../include/vstring.h
postscreen.o: postscreen.c
+postscreen.o: postscreen.h
+postscreen_dict.o: ../../include/addr_match_list.h
+postscreen_dict.o: ../../include/argv.h
+postscreen_dict.o: ../../include/dict.h
+postscreen_dict.o: ../../include/dict_cache.h
+postscreen_dict.o: ../../include/match_list.h
+postscreen_dict.o: ../../include/match_ops.h
+postscreen_dict.o: ../../include/msg.h
+postscreen_dict.o: ../../include/sys_defs.h
+postscreen_dict.o: ../../include/vbuf.h
+postscreen_dict.o: ../../include/vstream.h
+postscreen_dict.o: ../../include/vstring.h
+postscreen_dict.o: postscreen.h
+postscreen_dict.o: postscreen_dict.c
+postscreen_dnsbl.o: ../../include/addr_match_list.h
+postscreen_dnsbl.o: ../../include/argv.h
+postscreen_dnsbl.o: ../../include/attr.h
+postscreen_dnsbl.o: ../../include/connect.h
+postscreen_dnsbl.o: ../../include/dict.h
+postscreen_dnsbl.o: ../../include/dict_cache.h
+postscreen_dnsbl.o: ../../include/events.h
+postscreen_dnsbl.o: ../../include/htable.h
+postscreen_dnsbl.o: ../../include/iostuff.h
+postscreen_dnsbl.o: ../../include/mail_params.h
+postscreen_dnsbl.o: ../../include/mail_proto.h
+postscreen_dnsbl.o: ../../include/match_list.h
+postscreen_dnsbl.o: ../../include/match_ops.h
+postscreen_dnsbl.o: ../../include/msg.h
+postscreen_dnsbl.o: ../../include/mymalloc.h
+postscreen_dnsbl.o: ../../include/split_at.h
+postscreen_dnsbl.o: ../../include/sys_defs.h
+postscreen_dnsbl.o: ../../include/valid_hostname.h
+postscreen_dnsbl.o: ../../include/vbuf.h
+postscreen_dnsbl.o: ../../include/vstream.h
+postscreen_dnsbl.o: ../../include/vstring.h
+postscreen_dnsbl.o: postscreen.h
+postscreen_dnsbl.o: postscreen_dnsbl.c
/* are made in parallel.
/*
/* When the postscreen_greet_wait time has elapsed, and the
-/* SMTP client address is listed with at least one of these
-/* blocklists, this is logged as:
+/* combined DNSBL score is equal to or greater than the
+/* postscreen_dnsbl_threshold parameter value, this is logged
+/* as:
/* .sp
/* .nf
/* \fBDNSBL rank \fIcount \fBfor \fIaddress\fR
/* .fi
/* .sp
-/* Translation: the client at \fIaddress\fR is listed with
-/* \fIcount\fR DNSBL servers. The \fIcount\fR does not
-/* depend on the number of DNS records that an individual DNSBL
-/* server returns.
+/* Translation: the SMTP client at \fIaddress\fR has a combined
+/* DNSBL score of \fIcount\fR.
/*
/* The postscreen_dnsbl_action parameter specifies the action
-/* that is taken next:
+/* that is taken when the combined DNSBL score is equal to or
+/* greater than the threshold:
/* .IP "\fBcontinue\fR (default)"
/* Forward the connection to a real SMTP server process.
/* .IP \fBdrop\fR
/* Network addresses that are permanently blacklisted; see the
/* postscreen_blacklist_action parameter for possible actions.
/* .IP "\fBpostscreen_dnsbl_action (continue)\fR"
-/* The action that \fBpostscreen\fR(8) takes when an SMTP client is listed
-/* at the DNS blocklist domains specified with the postscreen_dnsbl_sites
-/* parameter.
+/* The action that \fBpostscreen\fR(8) takes when an SMTP client's combined
+/* DNSBL score is equal to or greater than a threshold (as defined
+/* with the postscreen_dnsbl_sites and postscreen_dnsbl_threshold
+/* parameters).
/* .IP "\fBpostscreen_dnsbl_sites (empty)\fR"
-/* Optional list of DNS blocklist domains.
+/* Optional list of DNS blocklist domains, filters and weight
+/* factors.
+/* .IP "\fBpostscreen_dnsbl_threshold (1)\fR"
+/* The inclusive lower bound for blocking an SMTP client, based on
+/* its combined DNSBL score as defined with the postscreen_dnsbl_sites
+/* parameter.
/* .IP "\fBpostscreen_greet_action (continue)\fR"
/* The action that \fBpostscreen\fR(8) takes when an SMTP client speaks
/* before its turn within the time specified with the postscreen_greet_wait
/* .fi
/* .IP "\fBpostscreen_cache_cleanup_interval (12h)\fR"
/* The amount of time between \fBpostscreen\fR(8) cache cleanup runs.
-/* .IP "\fBpostscreen_cache_map (btree:$data_directory/ps_whitelist)\fR"
+/* .IP "\fBpostscreen_cache_map (btree:$data_directory/ps_cache)\fR"
/* Persistent storage for the \fBpostscreen\fR(8) server decisions.
/* .IP "\fBpostscreen_cache_retention_time (1d)\fR"
/* The amount of time that \fBpostscreen\fR(8) will cache an expired
#include <mail_server.h>
+/* Application-specific. */
+
+#include <postscreen.h>
+
/*
* Configuration parameters.
*/
char *var_ps_blist_nets;
char *var_ps_greet_banner;
char *var_ps_blist_action;
+int var_ps_dnsbl_thresh;
/*
* Per-session state. See also: new_session_state() and free_event_state()
static VSTRING *temp; /* scratchpad */
static char *smtp_service_name; /* path to real SMTPD */
static char *teaser_greeting; /* spamware teaser banner */
-static ARGV *dnsbl_sites; /* dns blocklist domains */
-static VSTRING *reply_addr; /* address in DNSBL reply */
-static VSTRING *reply_domain; /* domain in DNSBL reply */
-static HTABLE *dnsbl_cache; /* entries being queried */
static int dnsbl_action; /* PS_ACT_DROP or PS_ACT_CONT */
static int greet_action; /* PS_ACT_DROP or PS_ACT_CONT */
static int hangup_action; /* PS_ACT_DROP or PS_ACT_CONT */
static ADDR_MATCH_LIST *blist_nets; /* permanently blacklisted networks */
static int blist_action; /* PS_ACT_DROP or PS_ACT_CONT */
- /*
- * See log_adhoc.c for discussion.
- */
-typedef struct {
- int dt_sec; /* make sure it's signed */
- int dt_usec; /* make sure it's signed */
-} DELTA_TIME;
-
-#define PS_CALC_DELTA(x, y, z) \
- do { \
- (x).dt_sec = (y).tv_sec - (z).tv_sec; \
- (x).dt_usec = (y).tv_usec - (z).tv_usec; \
- while ((x).dt_usec < 0) { \
- (x).dt_usec += 1000000; \
- (x).dt_sec -= 1; \
- } \
- while ((x).dt_usec >= 1000000) { \
- (x).dt_usec -= 1000000; \
- (x).dt_sec += 1; \
- } \
- if ((x).dt_sec < 0) \
- (x).dt_sec = (x).dt_usec = 0; \
- } while (0)
-
-#define SIG_DIGS 2
-
-/* READ_EVENT_REQUEST - prepare for transition to next state */
-
-#define READ_EVENT_REQUEST(fd, action, context, timeout) do { \
- if (msg_verbose) msg_info("%s: read-request fd=%d", myname, (fd)); \
- event_enable_read((fd), (action), (context)); \
- event_request_timer((action), (context), (timeout)); \
-} while (0)
-
-/* CLEAR_EVENT_REQUEST - complete state transition */
-
-#define CLEAR_EVENT_REQUEST(fd, action, context) do { \
- if (msg_verbose) msg_info("%s: clear-request fd=%d", myname, (fd)); \
- event_disable_readwrite(fd); \
- event_cancel_timer((action), (context)); \
-} while (0)
-
-/* SLMs. */
-
-#define STR(x) vstring_str(x)
-#define LEN(x) VSTRING_LEN(x)
-
- /*
- * Monitor time-critical operations.
- */
-#define PS_GET_TIME_BEFORE_LOOKUP \
- struct timeval _before, _after; \
- DELTA_TIME _delta; \
- GETTIMEOFDAY(&_before);
-
-#define PS_DELTA_MS(d) ((d).dt_sec * 1000 + (d).dt_usec / 1000)
-
-#define PS_CHECK_TIME_AFTER_LOOKUP(table, action) \
- GETTIMEOFDAY(&_after); \
- PS_CALC_DELTA(_delta, _after, _before); \
- if (_delta.dt_sec > 1 || _delta.dt_usec > 100000) \
- msg_warn("%s: %s %s took %d ms", \
- myname, (table), (action), PS_DELTA_MS(_delta));
-
-/* ps_addr_match_list_match - time-critical address list lookup */
-
-static int ps_addr_match_list_match(ADDR_MATCH_LIST *addr_list,
- const char *addr_str)
-{
- const char *myname = "ps_addr_match_list_match";
- int result;
-
- PS_GET_TIME_BEFORE_LOOKUP;
- result = addr_match_list_match(addr_list, addr_str);
- PS_CHECK_TIME_AFTER_LOOKUP("address list", "lookup");
- return (result);
-}
-
-/* ps_dict_get - time-critical table lookup */
-
-static const char *ps_dict_get(DICT_CACHE *cache, const char *key)
-{
- const char *myname = "ps_dict_get";
- const char *result;
-
- PS_GET_TIME_BEFORE_LOOKUP;
- result = dict_cache_lookup(cache, key);
- PS_CHECK_TIME_AFTER_LOOKUP(dict_cache_name(cache), "lookup");
- return (result);
-}
-
-/* ps_dict_put - table dictionary update */
-
-static void ps_dict_put(DICT_CACHE *cache, const char *key, const char *value)
-{
- const char *myname = "ps_dict_put";
-
- PS_GET_TIME_BEFORE_LOOKUP;
- dict_cache_update(cache, key, value);
- PS_CHECK_TIME_AFTER_LOOKUP(dict_cache_name(cache), "update");
-}
-
- /*
- * DNSBL lookup status per client IP address.
- */
-typedef struct {
- int dnsbl_count; /* is this address listed */
- int refcount; /* query reference count */
-} PS_DNSBL_ENTRY;
-
-/* postscreen_dnsbl_entry_create - create blocklist cache entry */
-
-static PS_DNSBL_ENTRY *postscreen_dnsbl_entry_create(void)
-{
- PS_DNSBL_ENTRY *entry;
-
- entry = (PS_DNSBL_ENTRY *) mymalloc(sizeof(*entry));
- entry->dnsbl_count = 0;
- entry->refcount = 0;
- return (entry);
-}
-
-/* postscreen_dnsbl_done - get blocklist cache entry, decrement refcount */
-
-static int postscreen_dnsbl_done(const char *addr)
-{
- const char *myname = "postscreen_dnsbl_done";
- PS_DNSBL_ENTRY *entry;
- int dnsbl_count;
-
- /*
- * Sanity check.
- */
- if ((entry = (PS_DNSBL_ENTRY *) htable_find(dnsbl_cache, addr)) == 0)
- msg_panic("%s: no blocklist cache entry for %s", myname, addr);
-
- /*
- * Yes, cache reads are destructive.
- */
- dnsbl_count = entry->dnsbl_count;
- entry->refcount -= 1;
- if (entry->refcount < 1) {
- if (msg_verbose)
- msg_info("%s: delete cache entry for %s", myname, addr);
- htable_delete(dnsbl_cache, addr, myfree);
- }
- return (dnsbl_count);
-}
-
-/* postscreen_dnsbl_reply - receive dnsbl reply, update blocklist cache entry */
-
-static void postscreen_dnsbl_reply(int event, char *context)
-{
- const char *myname = "postscreen_dnsbl_reply";
- VSTREAM *stream = (VSTREAM *) context;
- PS_DNSBL_ENTRY *entry;
- int dnsbl_count;
-
- CLEAR_EVENT_REQUEST(vstream_fileno(stream), postscreen_dnsbl_reply, context);
-
- /*
- * Later, this will become an UDP-based DNS client that is built directly
- * into the postscreen daemon.
- *
- * Don't panic when no blocklist cache entry exists. It may be gone when the
- * client triggered a "drop" action after pregreet, DNSBL lookup, or
- * hangup.
- */
- if (event == EVENT_READ
- && attr_scan(stream,
- ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
- ATTR_TYPE_STR, MAIL_ATTR_RBL_DOMAIN, reply_domain,
- ATTR_TYPE_STR, MAIL_ATTR_ADDR, reply_addr,
- ATTR_TYPE_INT, MAIL_ATTR_STATUS, &dnsbl_count,
- ATTR_TYPE_END) == 3) {
- if ((entry = (PS_DNSBL_ENTRY *)
- htable_find(dnsbl_cache, STR(reply_addr))) != 0)
- entry->dnsbl_count += dnsbl_count;
- }
- vstream_fclose(stream);
-}
-
-/* postscreen_dnsbl_query - send dnsbl query */
-
-static void postscreen_dnsbl_query(const char *addr)
-{
- const char *myname = "postscreen_dnsbl_query";
- int fd;
- VSTREAM *stream;
- char **cpp;
- PS_DNSBL_ENTRY *entry;
-
- /*
- * Avoid duplicate effort when this lookup is already in progress. Now,
- * we destroy the entry when the client replies. Later, we increment
- * refcounts with queries sent, and decrement refcounts with replies
- * received, so we can maintain state even after a client talks early,
- * and update the external cache asynchronously.
- */
- if ((entry = (PS_DNSBL_ENTRY *) htable_find(dnsbl_cache, addr)) != 0) {
- entry->refcount += 1;
- return;
- }
- if (msg_verbose)
- msg_info("%s: create cache entry for %s", myname, addr);
- entry = postscreen_dnsbl_entry_create();
- (void) htable_enter(dnsbl_cache, addr, (char *) entry);
- entry->refcount = 1;
-
- /*
- * Later, this will become an UDP-based DNS client that is built directly
- * into the postscreen daemon.
- */
- for (cpp = dnsbl_sites->argv; *cpp; cpp++) {
- if ((fd = LOCAL_CONNECT("private/" DNSBL_SERVICE, NON_BLOCKING, 1)) < 0) {
- msg_warn("%s: connect to " DNSBL_SERVICE " service: %m", myname);
- return;
- }
- stream = vstream_fdopen(fd, O_RDWR);
- attr_print(stream, ATTR_FLAG_NONE,
- ATTR_TYPE_STR, MAIL_ATTR_RBL_DOMAIN, *cpp,
- ATTR_TYPE_STR, MAIL_ATTR_ADDR, addr,
- ATTR_TYPE_END);
- if (vstream_fflush(stream) != 0) {
- msg_warn("%s: error sending to " DNSBL_SERVICE " service: %m", myname);
- vstream_fclose(stream);
- return;
- }
- READ_EVENT_REQUEST(vstream_fileno(stream), postscreen_dnsbl_reply,
- (char *) stream, DNSBLOG_TIMEOUT);
- }
-}
-
/* new_session_state - fill in connection state for event processing */
static PS_STATE *new_session_state(VSTREAM *stream, const char *addr,
vstream_fileno(state->smtp_client_stream)) < 0) {
msg_warn("cannot pass connection to service %s: %m", smtp_service_name);
smtp_reply(vstream_fileno(state->smtp_client_stream), state->smtp_client_addr,
- state->smtp_client_port, "421 4.3.2 No system resources\r\n");
+ state->smtp_client_port, "421 4.3.2 No system resources\r\n");
free_session_state(state);
return;
} else {
}
}
-/* smtp_read_event - handle pre-greet, EOF or timeout. */
+/* smtp_early_event - handle pre-greet, EOF or timeout. */
-static void smtp_read_event(int event, char *context)
+static void smtp_early_event(int event, char *context)
{
- const char *myname = "smtp_read_event";
+ const char *myname = "smtp_early_event";
PS_STATE *state = (PS_STATE *) context;
char read_buf[PS_READ_BUF_SIZE];
int read_count;
- int dnsbl_count;
+ int dnsbl_score;
int elapsed;
int action;
* was closed, or we reached the limit of our patience.
*/
CLEAR_EVENT_REQUEST(vstream_fileno(state->smtp_client_stream),
- smtp_read_event, context);
+ smtp_early_event, context);
/*
* If this session ends here, we MUST read the blocklist cache otherwise
*/
case EVENT_TIME:
if (*var_ps_dnsbl_sites)
- dnsbl_count = postscreen_dnsbl_done(state->smtp_client_addr);
+ dnsbl_score = ps_dnsbl_retrieve(state->smtp_client_addr);
else
- dnsbl_count = 0;
- if (dnsbl_count > 0) {
+ dnsbl_score = 0;
+ if (dnsbl_score >= var_ps_dnsbl_thresh) {
msg_info("DNSBL rank %d for %s",
- dnsbl_count, state->smtp_client_addr);
+ dnsbl_score, state->smtp_client_addr);
if (dnsbl_action == PS_ACT_DROP) {
smtp_reply(vstream_fileno(state->smtp_client_stream),
state->smtp_client_addr, state->smtp_client_port,
"OLD" : "NEW", state->smtp_client_addr);
if (cache_map != 0) {
vstring_sprintf(temp, "%ld", (long) event_time());
- ps_dict_put(cache_map, state->smtp_client_addr, STR(temp));
+ ps_cache_update(cache_map, state->smtp_client_addr, STR(temp));
}
}
send_socket(state);
}
if (action == PS_ACT_DROP) {
if (*var_ps_dnsbl_sites)
- (void) postscreen_dnsbl_done(state->smtp_client_addr);
+ (void) ps_dnsbl_retrieve(state->smtp_client_addr);
free_session_state(state);
} else {
state->flags |= PS_FLAG_NOCACHE;
- /* not: postscreen_dnsbl_done */
+ /* not: ps_dnsbl_retrieve */
if (elapsed > var_ps_greet_wait)
elapsed = var_ps_greet_wait;
- event_request_timer(smtp_read_event, context,
+ event_request_timer(smtp_early_event, context,
var_ps_greet_wait - elapsed);
}
break;
* lowest precedence.
*/
else if (cache_map != 0
- && (stamp_str = ps_dict_get(cache_map, smtp_client_addr.buf)) != 0) {
+ && (stamp_str = ps_cache_lookup(cache_map, smtp_client_addr.buf)) != 0) {
stamp_time = strtoul(stamp_str, 0, 10);
if (stamp_time > event_time() - var_ps_cache_ttl) {
msg_info("PASS OLD %s", smtp_client_addr.buf);
smtp_client_port.buf);
state->flags |= state_flags;
READ_EVENT_REQUEST(vstream_fileno(state->smtp_client_stream),
- smtp_read_event, (char *) state, var_ps_greet_wait);
+ smtp_early_event, (char *) state, var_ps_greet_wait);
/*
* Run a DNS blocklist query while we wait for the client to respond.
*/
if (*var_ps_dnsbl_sites)
- postscreen_dnsbl_query(smtp_client_addr.buf);
+ ps_dnsbl_request(smtp_client_addr.buf);
}
/* postscreen_cache_validator - validate one cache entry */
vstring_sprintf(temp, "220-%s\r\n", var_ps_greet_banner);
teaser_greeting = mystrdup(STR(temp));
}
- dnsbl_sites = argv_split(var_ps_dnsbl_sites, ", \t\r\n");
- dnsbl_cache = htable_create(13);
- reply_addr = vstring_alloc(100);
- reply_domain = vstring_alloc(100);
+ ps_dnsbl_init();
if ((blist_action = name_code(actions, NAME_CODE_FLAG_NONE,
var_ps_blist_action)) < 0)
msg_fatal("bad %s value: %s", VAR_PS_BLIST_ACTION, var_ps_blist_action);
};
static const CONFIG_INT_TABLE int_table[] = {
VAR_PROC_LIMIT, DEF_PROC_LIMIT, &var_proc_limit, 1, 0,
+ VAR_PS_DNSBL_THRESH, DEF_PS_DNSBL_THRESH, &var_ps_dnsbl_thresh, 0, 0,
0,
};
static const CONFIG_NINT_TABLE nint_table[] = {
--- /dev/null
+/*++
+/* NAME
+/* postscreen 3h
+/* SUMMARY
+/* postscreen internal interfaces
+/* SYNOPSIS
+/* #include <postscreen.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict_cache.h>
+
+ /*
+ * Global library.
+ */
+#include <addr_match_list.h>
+
+ /*
+ * See log_adhoc.c for discussion.
+ */
+typedef struct {
+ int dt_sec; /* make sure it's signed */
+ int dt_usec; /* make sure it's signed */
+} DELTA_TIME;
+
+#define PS_CALC_DELTA(x, y, z) \
+ do { \
+ (x).dt_sec = (y).tv_sec - (z).tv_sec; \
+ (x).dt_usec = (y).tv_usec - (z).tv_usec; \
+ while ((x).dt_usec < 0) { \
+ (x).dt_usec += 1000000; \
+ (x).dt_sec -= 1; \
+ } \
+ while ((x).dt_usec >= 1000000) { \
+ (x).dt_usec -= 1000000; \
+ (x).dt_sec += 1; \
+ } \
+ if ((x).dt_sec < 0) \
+ (x).dt_sec = (x).dt_usec = 0; \
+ } while (0)
+
+#define SIG_DIGS 2
+
+/* READ_EVENT_REQUEST - prepare for transition to next state */
+
+#define READ_EVENT_REQUEST(fd, action, context, timeout) do { \
+ if (msg_verbose) msg_info("%s: read-request fd=%d", myname, (fd)); \
+ event_enable_read((fd), (action), (context)); \
+ event_request_timer((action), (context), (timeout)); \
+} while (0)
+
+/* CLEAR_EVENT_REQUEST - complete state transition */
+
+#define CLEAR_EVENT_REQUEST(fd, action, context) do { \
+ if (msg_verbose) msg_info("%s: clear-request fd=%d", myname, (fd)); \
+ event_disable_readwrite(fd); \
+ event_cancel_timer((action), (context)); \
+} while (0)
+
+ /*
+ * SLMs.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+ /*
+ * postscreen_dict.c
+ */
+extern int ps_addr_match_list_match(ADDR_MATCH_LIST *, const char *);
+extern const char *ps_cache_lookup(DICT_CACHE *, const char *);
+extern void ps_cache_update(DICT_CACHE *, const char *, const char *);
+
+ /*
+ * postscreen_dnsbl.c
+ */
+extern void ps_dnsbl_init(void);
+extern int ps_dnsbl_retrieve(const char *);
+extern void ps_dnsbl_request(const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
--- /dev/null
+/*++
+/* NAME
+/* postscreen_dict 3
+/* SUMMARY
+/* postscreen table access wrappers
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* int ps_addr_match_list_match(match_list, client_addr)
+/* ADDR_MATCH_LIST *match_list;
+/* const char *client_addr;
+/*
+/* const char *ps_cache_lookup(DICT_CACHE *cache, const char *key)
+/* DICT_CACHE *cache;
+/* const char *key;
+/*
+/* void ps_cache_update(cache, key, value)
+/* DICT_CACHE *cache;
+/* const char *key;
+/* const char *value;
+/* DESCRIPTION
+/* This module implements wrappers around time-critical table
+/* access functions. The functions log a warning when table
+/* access takes a non-trivial amount of time.
+/*
+/* ps_addr_match_list_match() is a wrapper around
+/* addr_match_list_match().
+/*
+/* ps_cache_lookup() and ps_cache_update() are wrappers around
+/* the corresponding dict_cache() methods.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+
+/* Utility library. */
+
+#include <msg.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+ /*
+ * Monitor time-critical operations.
+ */
+#define PS_GET_TIME_BEFORE_LOOKUP \
+ struct timeval _before, _after; \
+ DELTA_TIME _delta; \
+ GETTIMEOFDAY(&_before);
+
+#define PS_DELTA_MS(d) ((d).dt_sec * 1000 + (d).dt_usec / 1000)
+
+#define PS_CHECK_TIME_AFTER_LOOKUP(table, action) \
+ GETTIMEOFDAY(&_after); \
+ PS_CALC_DELTA(_delta, _after, _before); \
+ if (_delta.dt_sec > 1 || _delta.dt_usec > 100000) \
+ msg_warn("%s: %s %s took %d ms", \
+ myname, (table), (action), PS_DELTA_MS(_delta));
+
+/* ps_addr_match_list_match - time-critical address list lookup */
+
+int ps_addr_match_list_match(ADDR_MATCH_LIST *addr_list,
+ const char *addr_str)
+{
+ const char *myname = "ps_addr_match_list_match";
+ int result;
+
+ PS_GET_TIME_BEFORE_LOOKUP;
+ result = addr_match_list_match(addr_list, addr_str);
+ PS_CHECK_TIME_AFTER_LOOKUP("address list", "lookup");
+ return (result);
+}
+
+/* ps_cache_lookup - time-critical cache lookup */
+
+const char *ps_cache_lookup(DICT_CACHE *cache, const char *key)
+{
+ const char *myname = "ps_cache_lookup";
+ const char *result;
+
+ PS_GET_TIME_BEFORE_LOOKUP;
+ result = dict_cache_lookup(cache, key);
+ PS_CHECK_TIME_AFTER_LOOKUP(dict_cache_name(cache), "lookup");
+ return (result);
+}
+
+/* ps_cache_update - time-critical cache update */
+
+void ps_cache_update(DICT_CACHE *cache, const char *key, const char *value)
+{
+ const char *myname = "ps_cache_update";
+
+ PS_GET_TIME_BEFORE_LOOKUP;
+ dict_cache_update(cache, key, value);
+ PS_CHECK_TIME_AFTER_LOOKUP(dict_cache_name(cache), "update");
+}
--- /dev/null
+/*++
+/* NAME
+/* postscreen_dnsbl 3
+/* SUMMARY
+/* postscreen DNSBL support
+/* SYNOPSIS
+/* #include <postscreen.h>
+/*
+/* void ps_dnsbl_init(void)
+/*
+/* void ps_dnsbl_request(client_addr)
+/* char *client_addr;
+/*
+/* int ps_dnsbl_retrieve(client_addr)
+/* char *client_addr;
+/* DESCRIPTION
+/* This module implements preliminary support for DNSBL lookups
+/* that complete in the background. Multiple requests for the
+/* same information are handled with reference counts.
+/*
+/* ps_dnsbl_init() initializes this module, and must be called
+/* once before any of the other functions in this module.
+/*
+/* ps_dnsbl_request() requests a blocklist score for the specified
+/* client IP address and increments the reference count. The
+/* client IP address must be in inet_ntop(3) output format.
+/*
+/* ps_dnsbl_retrieve() retrieves the result score requested with
+/* ps_dnsbl_request() and decrements the reference count. It
+/* is an error to retrieve a score without requesting it first.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <stdio.h> /* sscanf */
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <argv.h>
+#include <htable.h>
+#include <events.h>
+#include <vstream.h>
+#include <connect.h>
+#include <split_at.h>
+#include <valid_hostname.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include <postscreen.h>
+
+#define DNSBL_SERVICE "dnsblog"
+#define DNSBLOG_TIMEOUT 10
+
+ /*
+ * Per-DNSBL filters and weights.
+ *
+ * The postscreen_dnsbl_sites parameter specifies zero or more DNSBL domains.
+ * We provide multiple access methods, one for quick iteration when sending
+ * queries to all DNSBL servers, and one for quick location when receiving a
+ * reply from one DNSBL server.
+ *
+ * Each DNSBL domain can be specified more than once, each time with a
+ * different (filter, weight) pair. We group (filter, weight) pairs in a
+ * linked list under their DNSBL domain name.
+ */
+static HTABLE *dnsbl_site_cache; /* indexed by DNSBNL domain */
+static HTABLE_INFO **dnsbl_site_list; /* flattened cache */
+
+typedef struct PS_DNSBL_SITE {
+ char *filter; /* reply filter (default: null) */
+ int weight; /* reply weight (default: 1) */
+ struct PS_DNSBL_SITE *next; /* linked list */
+} PS_DNSBL_SITE;
+
+ /*
+ * Per-client DNSBL scores.
+ *
+ * One remote SMTP client may make parallel connections and thereby trigger
+ * parallel blocklist score requests. We combine identical requests under
+ * the client IP address in a single reference-counted entry. The reference
+ * count goes up with each score request, and it goes down with each score
+ * retrieval.
+ */
+static HTABLE *dnsbl_score_cache; /* indexed by client address */
+
+typedef struct {
+ int total; /* combined blocklist score */
+ int refcount; /* score reference count */
+} PS_DNSBL_SCORE;
+
+ /*
+ * Per-request state.
+ *
+ * This implementation stores the client IP address and DNSBL domain in the
+ * DNSBLOG query/reply stream. This simplifies code, and allows the DNSBLOG
+ * server to produce more informative logging.
+ */
+static VSTRING *reply_client; /* client address in DNSBLOG reply */
+static VSTRING *reply_dnsbl; /* domain in DNSBLOG reply */
+static VSTRING *reply_addr; /* adress list in DNSBLOG reply */
+
+/* ps_dnsbl_add_site - add DNSBL site information */
+
+static void ps_dnsbl_add_site(const char *site)
+{
+ const char *myname = "ps_dnsbl_add_site";
+ char *saved_site = mystrdup(site);
+ PS_DNSBL_SITE *old_site;
+ PS_DNSBL_SITE *new_site;
+ char junk;
+ const char *weight_text;
+ const char *pattern_text;
+ int weight;
+
+ /*
+ * Parse the required DNSBL domain name, the optional reply filter and
+ * the optional reply weight factor.
+ */
+#define DO_GRIPE 1
+
+ /* Negative weight means whitelist. */
+ if ((weight_text = split_at(saved_site, '*')) != 0) {
+ if (sscanf(weight_text, "%d%c", &weight, &junk) != 1)
+ msg_fatal("bad DNSBL weight factor \"%s\" in \"%s\"",
+ weight_text, site);
+ } else {
+ weight = 1;
+ }
+ /* Preliminary fixed-string filter. */
+ if ((pattern_text = split_at(saved_site, '=')) != 0) {
+ if (valid_ipv4_hostaddr(pattern_text, DO_GRIPE) == 0)
+ msg_fatal("bad DNSBL filter syntax \"%s\" in \"%s\"",
+ pattern_text, site);
+ }
+ if (valid_hostname(saved_site, DO_GRIPE) == 0)
+ msg_fatal("bad DNSBL domain name \"%s\" in \"%s\"",
+ saved_site, site);
+
+ if (msg_verbose)
+ msg_info("%s: \"%s\" -> domain=\"%s\" pattern=\"%s\" weight=%d",
+ myname, site, saved_site, pattern_text ? pattern_text :
+ "null", weight);
+
+ /*
+ * Add a new node for this DNSBL domain name. One DNSBL domain name can
+ * be specified multiple times with different filters and weights. These
+ * are stored as a linked list under the DNSBL domain name.
+ */
+ new_site = (PS_DNSBL_SITE *) mymalloc(sizeof(*new_site));
+ new_site->filter = (pattern_text ? mystrdup(pattern_text) : 0);
+ new_site->weight = weight;
+
+ if ((old_site = (PS_DNSBL_SITE *)
+ htable_find(dnsbl_site_cache, saved_site)) != 0) {
+ new_site->next = old_site->next;
+ old_site->next = new_site;
+ } else {
+ (void) htable_enter(dnsbl_site_cache, saved_site, (char *) new_site);
+ new_site->next = 0;
+ }
+ myfree(saved_site);
+}
+
+/* ps_dnsbl_match - match DNSBL reply filter */
+
+static int ps_dnsbl_match(const char *filter, ARGV *reply)
+{
+ char **cpp;
+
+ /*
+ * Preliminary fixed-string implementation.
+ */
+ for (cpp = reply->argv; *cpp != 0; cpp++)
+ if (strcmp(filter, *cpp) == 0)
+ return (1);
+ return (0);
+}
+
+/* ps_dnsbl_retrieve - retrieve blocklist score, decrement reference count */
+
+int ps_dnsbl_retrieve(const char *client_addr)
+{
+ const char *myname = "ps_dnsbl_retrieve";
+ PS_DNSBL_SCORE *score;
+ int result_score;
+
+ /*
+ * Sanity check.
+ */
+ if ((score = (PS_DNSBL_SCORE *)
+ htable_find(dnsbl_score_cache, client_addr)) == 0)
+ msg_panic("%s: no blocklist score for %s", myname, client_addr);
+
+ /*
+ * Reads are destructive.
+ */
+ result_score = score->total;
+ score->refcount -= 1;
+ if (score->refcount < 1) {
+ if (msg_verbose)
+ msg_info("%s: delete blocklist score for %s", myname, client_addr);
+ htable_delete(dnsbl_score_cache, client_addr, myfree);
+ }
+ return (result_score);
+}
+
+/* ps_dnsbl_receive - receive DNSBL reply, update blocklist score */
+
+static void ps_dnsbl_receive(int event, char *context)
+{
+ const char *myname = "ps_dnsbl_receive";
+ VSTREAM *stream = (VSTREAM *) context;
+ PS_DNSBL_SCORE *score;
+ PS_DNSBL_SITE *site;
+ ARGV *reply_argv;
+
+ CLEAR_EVENT_REQUEST(vstream_fileno(stream), ps_dnsbl_receive, context);
+
+ /*
+ * Receive the DNSBL lookup result.
+ *
+ * This is preliminary code to explore the field. Later, DNSBL lookup will
+ * be handled by an UDP-based DNS client that is built directly into some
+ * Postfix daemon.
+ *
+ * Don't bother looking up the blocklist score when the client IP address is
+ * not listed at the DNSBL.
+ *
+ * Don't panic when the blocklist score no longer exists. It may be deleted
+ * when the client triggers a "drop" action after pregreet, when the
+ * client does not pregreet and the DNSBL reply arrives late, or when the
+ * client triggers a "drop" action after hanging up.
+ */
+ if (event == EVENT_READ
+ && attr_scan(stream,
+ ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
+ ATTR_TYPE_STR, MAIL_ATTR_RBL_DOMAIN, reply_dnsbl,
+ ATTR_TYPE_STR, MAIL_ATTR_ACT_CLIENT_ADDR, reply_client,
+ ATTR_TYPE_STR, MAIL_ATTR_RBL_ADDR, reply_addr,
+ ATTR_TYPE_END) == 3
+ && *STR(reply_addr) != 0
+ && (score = (PS_DNSBL_SCORE *)
+ htable_find(dnsbl_score_cache, STR(reply_client))) != 0) {
+
+ /*
+ * Run this response past all applicable DNSBL filters and update the
+ * blocklist score for this client IP address.
+ *
+ * Don't panic when the DNSBL domain name is not found. The DNSBLOG
+ * server may be messed up.
+ */
+ if (msg_verbose)
+ msg_info("%s: client=\"%s\" score=%d domain=\"%s\" reply=\"%s\"",
+ myname, STR(reply_client), score->total,
+ STR(reply_dnsbl), STR(reply_addr));
+ for (reply_argv = 0, site = (PS_DNSBL_SITE *)
+ htable_find(dnsbl_site_cache, STR(reply_dnsbl));
+ site != 0; site = site->next) {
+ if (site->filter == 0
+ || ps_dnsbl_match(site->filter, reply_argv ? reply_argv :
+ (reply_argv = argv_split(STR(reply_addr), " ")))) {
+ score->total += site->weight;
+ if (msg_verbose)
+ msg_info("%s: filter=\"%s\" weight=%d score=%d",
+ myname, site->filter ? site->filter : "null",
+ site->weight, score->total);
+ }
+ }
+ if (reply_argv != 0)
+ argv_free(reply_argv);
+ }
+ vstream_fclose(stream);
+}
+
+/* ps_dnsbl_request - send dnsbl query, increment reference count */
+
+void ps_dnsbl_request(const char *client_addr)
+{
+ const char *myname = "ps_dnsbl_request";
+ int fd;
+ VSTREAM *stream;
+ HTABLE_INFO **ht;
+ PS_DNSBL_SCORE *score;
+
+ /*
+ * Avoid duplicate effort when this lookup is already in progress. We
+ * store a reference-counted DNSBL score under its client IP address. We
+ * increment the reference count with each request, and decrement the
+ * reference count with each retrieval.
+ */
+ if ((score = (PS_DNSBL_SCORE *)
+ htable_find(dnsbl_score_cache, client_addr)) != 0) {
+ score->refcount += 1;
+ return;
+ }
+ if (msg_verbose)
+ msg_info("%s: create blocklist score for %s", myname, client_addr);
+ score = (PS_DNSBL_SCORE *) mymalloc(sizeof(*score));
+ score->total = 0;
+ score->refcount = 1;
+ (void) htable_enter(dnsbl_score_cache, client_addr, (char *) score);
+
+ /*
+ * Send a query to all DNSBL servers. Later, DNSBL lookup will be done
+ * with an UDP-based DNS client that is built directly into Postfix code.
+ * We therefore do not optimize the maximum out of this temporary
+ * implementation.
+ */
+ for (ht = dnsbl_site_list; *ht; ht++) {
+ if ((fd = LOCAL_CONNECT("private/" DNSBL_SERVICE, NON_BLOCKING, 1)) < 0) {
+ msg_warn("%s: connect to " DNSBL_SERVICE " service: %m", myname);
+ return;
+ }
+ stream = vstream_fdopen(fd, O_RDWR);
+ attr_print(stream, ATTR_FLAG_NONE,
+ ATTR_TYPE_STR, MAIL_ATTR_RBL_DOMAIN, ht[0]->key,
+ ATTR_TYPE_STR, MAIL_ATTR_ACT_CLIENT_ADDR, client_addr,
+ ATTR_TYPE_END);
+ if (vstream_fflush(stream) != 0) {
+ msg_warn("%s: error sending to " DNSBL_SERVICE " service: %m", myname);
+ vstream_fclose(stream);
+ return;
+ }
+ READ_EVENT_REQUEST(vstream_fileno(stream), ps_dnsbl_receive,
+ (char *) stream, DNSBLOG_TIMEOUT);
+ }
+}
+
+/* ps_dnsbl_init - initialize */
+
+void ps_dnsbl_init(void)
+{
+ const char *myname = "ps_dnsbl_init";
+ ARGV *dnsbl_site = argv_split(var_ps_dnsbl_sites, ", \t\r\n");
+ char **cpp;
+
+ /*
+ * Sanity check.
+ */
+ if (dnsbl_site_cache != 0)
+ msg_panic("%s: called more than once", myname);
+
+ /*
+ * Prepare for quick iteration when sending out queries to all DNSBL
+ * servers, and for quick lookup when a reply arrives from a specific
+ * DNSBL server.
+ */
+ dnsbl_site_cache = htable_create(13);
+ for (cpp = dnsbl_site->argv; *cpp; cpp++)
+ ps_dnsbl_add_site(*cpp);
+ argv_free(dnsbl_site);
+ dnsbl_site_list = htable_list(dnsbl_site_cache);
+
+ /*
+ * The per-client blocklist score.
+ */
+ dnsbl_score_cache = htable_create(13);
+
+ /*
+ * Space for ad-hoc DNSBLOG server request/reply parameters.
+ */
+ reply_client = vstring_alloc(100);
+ reply_dnsbl = vstring_alloc(100);
+ reply_addr = vstring_alloc(100);
+}