diff --git a/postfix/.indent.pro b/postfix/.indent.pro
index bb92580f8..b07186824 100644
--- a/postfix/.indent.pro
+++ b/postfix/.indent.pro
@@ -191,8 +191,8 @@
-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
diff --git a/postfix/HISTORY b/postfix/HISTORY
index 6eb9d6e4a..038bf8aeb 100644
--- a/postfix/HISTORY
+++ b/postfix/HISTORY
@@ -15908,3 +15908,30 @@ Apologies for any names omitted.
"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.
diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES
index ee2f3f90f..a3f0f9ea2 100644
--- a/postfix/RELEASE_NOTES
+++ b/postfix/RELEASE_NOTES
@@ -14,6 +14,46 @@ specifies the release date of a stable release or snapshot release.
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
======================================
diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html
index c5a4be1a4..ef30d5b9b 100644
--- a/postfix/html/postconf.5.html
+++ b/postfix/html/postconf.5.html
@@ -6623,8 +6623,9 @@ parameter. Specify one of the following:
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.
@@ -6678,7 +6679,7 @@ seconds.
postscreen_cache_map
-(default: btree:$data_directory/ps_whitelist)
+(default: btree:$data_directory/ps_cache)
Persistent storage for the postscreen(8) server decisions.
@@ -6724,9 +6725,10 @@ unit).
postscreen_dnsbl_action
(default: continue)
-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:
+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:
@@ -6748,14 +6750,70 @@ parameter. Specify one of the following:
- postscreen_dnsbl_sites
(default: empty)
-
-
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.
+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.
+
+ 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.
+
+ Specify a list of domain=filter*weight entries, separated by
+comma or whitespace.
+
+
+
+-
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.
+
+ -
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.
+
+ -
When one postscreen_dnsbl_sites entry produces multiple
+DNSBL responses, postscreen(8) applies the weight at most once.
+
+
+
+
+ Examples:
+
+ To use example.com as a high-confidence blocklist, and to
+block mail with example.net and example.org only when both agree:
+
+
+
+postscreen_dnsbl_threshold = 2
+postscreen_dnsbl_sites = example.com*2, example.net, example.org
+
+
+ To filter only DNSBL replies containing 127.0.0.4:
+
+
+postscreen_dnsbl_sites = example.com=127.0.0.4
+
This feature is available in Postfix 2.8.
+
+
+- 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.
+
+ This feature is available in Postfix 2.8.
+
+
- postscreen_greet_action
@@ -6770,10 +6828,11 @@ parameter. Specify one of the following:
- 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.
drop
@@ -6836,9 +6895,11 @@ without sending data, within the time specified with the
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.
+
drop
@@ -8617,17 +8678,17 @@ discard EHLO keywords selectively.
(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.
- res_defnames
-- 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.
- res_dnsrch
diff --git a/postfix/html/postscreen.8.html b/postfix/html/postscreen.8.html
index b90d47a14..0620620cf 100644
--- a/postfix/html/postscreen.8.html
+++ b/postfix/html/postscreen.8.html
@@ -182,30 +182,31 @@ POSTSCREEN(8) POSTSCREEN(8)
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:
DNSBL rank count for address
- Translation: the client at address is listed with count
- DNSBL servers. The count does not depend on the number of
- DNS records that an individual DNSBL server returns.
+ Translation: the SMTP client at address has a combined
+ DNSBL score of count.
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:
continue (default)
Forward the connection to a real SMTP server
process.
- drop 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.
+ drop 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.
SECURITY
The postscreen(8) 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.
STANDARDS
@@ -216,34 +217,42 @@ POSTSCREEN(8) POSTSCREEN(8)
Problems and transactions are logged to syslogd(8).
CONFIGURATION PARAMETERS
- Changes to main.cf are not picked up automatically, as
- postscreen(8) processes may run for several hours. Use
+ Changes to main.cf are not picked up automatically, as
+ postscreen(8) 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
postconf(5) for more details including examples.
TRIAGE PARAMETERS
postscreen_blacklist_action (continue)
- The action that postscreen(8) takes when an SMTP
- client is permanently blacklisted with the
+ The action that postscreen(8) takes when an SMTP
+ client is permanently blacklisted with the
postscreen_blacklist_networks parameter.
postscreen_blacklist_networks (empty)
Network addresses that are permanently blacklisted;
- see the postscreen_blacklist_action parameter for
+ see the postscreen_blacklist_action parameter for
possible actions.
postscreen_dnsbl_action (continue)
- The action that postscreen(8) takes when an SMTP
- client is listed at the DNS blocklist domains spec-
- ified with the postscreen_dnsbl_sites parameter.
+ 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_thresh-
+ old parameters).
postscreen_dnsbl_sites (empty)
- Optional list of DNS blocklist domains.
+ Optional list of DNS blocklist domains, filters and
+ weight factors.
+
+ postscreen_dnsbl_threshold (1)
+ The inclusive lower bound for blocking an SMTP
+ client, based on its combined DNSBL score as
+ defined with the postscreen_dnsbl_sites parameter.
postscreen_greet_action (continue)
- The action that postscreen(8) takes when an SMTP
+ The action that postscreen(8) takes when an SMTP
client speaks before its turn within the time spec-
ified with the postscreen_greet_wait parameter.
@@ -251,72 +260,72 @@ POSTSCREEN(8) POSTSCREEN(8)
The text in the optional "220-text..." server
response that postscreen(8) 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).
postscreen_greet_wait (4s)
The amount of time that postscreen(8) 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.
postscreen_hangup_action (continue)
- The action that postscreen(8) takes when an SMTP
+ The action that postscreen(8) takes when an SMTP
client disconnects without sending data, within the
- time specified with the postscreen_greet_wait
+ time specified with the postscreen_greet_wait
parameter.
postscreen_post_queue_limit ($default_process_limit)
- 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.
postscreen_pre_queue_limit ($default_process_limit)
- 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.
postscreen_whitelist_networks ($mynetworks)
Network addresses that are permanently whitelisted,
- and that will not be subjected to postscreen(8)
+ and that will not be subjected to postscreen(8)
checks.
smtpd_service (smtpd)
- The internal service that postscreen(8) forwards
+ The internal service that postscreen(8) forwards
allowed connections to.
CACHE CONTROLS
postscreen_cache_cleanup_interval (12h)
- The amount of time between postscreen(8) cache
+ The amount of time between postscreen(8) cache
cleanup runs.
- postscreen_cache_map (btree:$data_directory/ps_whitelist)
- Persistent storage for the postscreen(8) server
+ postscreen_cache_map (btree:$data_directory/ps_cache)
+ Persistent storage for the postscreen(8) server
decisions.
postscreen_cache_retention_time (1d)
The amount of time that postscreen(8) will cache an
- expired temporary whitelist entry before it is
+ expired temporary whitelist entry before it is
removed.
postscreen_cache_ttl (1d)
- The amount of time that postscreen(8) will cache a
+ The amount of time that postscreen(8) will cache a
decision for a specific SMTP client IP address.
MISCELLANEOUS CONTROLS
config_directory (see 'postconf -d' output)
- The default location of the Postfix main.cf and
+ The default location of the Postfix main.cf and
master.cf configuration files.
daemon_timeout (18000s)
- 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.
delay_logging_resolution_limit (2)
- The maximal number of digits after the decimal
+ The maximal number of digits after the decimal
point when logging sub-second delay values.
command_directory (see 'postconf -d' output)
- The location of all postfix administrative com-
+ The location of all postfix administrative com-
mands.
ipc_timeout (3600s)
@@ -324,24 +333,24 @@ POSTSCREEN(8) POSTSCREEN(8)
over an internal communication channel.
max_idle (100s)
- 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.
process_id (read-only)
- The process ID of a Postfix command or daemon
+ The process ID of a Postfix command or daemon
process.
process_name (read-only)
- The process name of a Postfix command or daemon
+ The process name of a Postfix command or daemon
process.
syslog_facility (mail)
The syslog facility of Postfix logging.
syslog_name (see 'postconf -d' output)
- 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".
SEE ALSO
@@ -350,7 +359,7 @@ POSTSCREEN(8) POSTSCREEN(8)
syslogd(8), system logging
LICENSE
- The Secure Mailer license must be distributed with this
+ The Secure Mailer license must be distributed with this
software.
AUTHOR(S)
diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5
index 63d69b9b1..cae19dc47 100644
--- a/postfix/man/man5/postconf.5
+++ b/postfix/man/man5/postconf.5
@@ -3726,8 +3726,9 @@ parameter. Specify one of the following:
.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
@@ -3759,7 +3760,7 @@ Time units: s (seconds), m (minutes), h (hours), d (days), w
(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.
@@ -3786,9 +3787,10 @@ Time units: s (seconds), m (minutes), h (hours), d (days), w
.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"
@@ -3796,10 +3798,62 @@ Drop the connection with a 521 SMTP reply.
.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)
@@ -3808,10 +3862,11 @@ before its turn within the time specified with the postscreen_greet_wait
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.
@@ -3847,9 +3902,10 @@ without sending data, within the time specified with the
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.
@@ -4903,13 +4959,13 @@ Use the smtp_discard_ehlo_keyword_address_maps feature to
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
diff --git a/postfix/man/man8/postscreen.8 b/postfix/man/man8/postscreen.8
index ea3e7d941..aa890119b 100644
--- a/postfix/man/man8/postscreen.8
+++ b/postfix/man/man8/postscreen.8
@@ -199,20 +199,20 @@ specifies a list of DNS blocklist servers. These lookups
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
@@ -262,11 +262,17 @@ parameter.
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
@@ -305,7 +311,7 @@ connections to.
.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
diff --git a/postfix/mantools/postlink b/postfix/mantools/postlink
index b52f16cc2..e8e29fab1 100755
--- a/postfix/mantools/postlink
+++ b/postfix/mantools/postlink
@@ -916,6 +916,7 @@ while (<>) {
s;\bpostscreen_greet_wait\b;$&;g;
s;\bpostscreen_greet_action\b;$&;g;
s;\bpostscreen_dnsbl_sites\b;$&;g;
+ s;\bpostscreen_dnsbl_threshold\b;$&;g;
s;\bpostscreen_dnsbl_action\b;$&;g;
s;\bpostscreen_hangup_action\b;$&;g;
s;\bpostscreen_whitelist_networks\b;$&;g;
diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto
index e2d6e6215..f1bccef33 100644
--- a/postfix/proto/postconf.proto
+++ b/postfix/proto/postconf.proto
@@ -12512,7 +12512,7 @@ inspection for DKIM-signed mail from known friendly domains.
This feature is available in Postfix 2.7, and as an optional
patch for Postfix 2.6.
-%PARAM postscreen_cache_map btree:$data_directory/ps_whitelist
+%PARAM postscreen_cache_map btree:$data_directory/ps_cache
Persistent storage for the postscreen(8) server decisions.
@@ -12604,18 +12604,63 @@ an optional one-letter suffix that specifies the time unit).
%PARAM postscreen_dnsbl_sites
-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.
+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.
+
+ 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.
+
+ Specify a list of domain=filter*weight entries, separated by
+comma or whitespace.
+
+
+
+-
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.
+
+ -
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.
+
+ -
When one postscreen_dnsbl_sites entry produces multiple
+DNSBL responses, postscreen(8) applies the weight at most once.
+
+
+
+
+ Examples:
+
+ To use example.com as a high-confidence blocklist, and to
+block mail with example.net and example.org only when both agree:
+
+
+
+postscreen_dnsbl_threshold = 2
+postscreen_dnsbl_sites = example.com*2, example.net, example.org
+
+
+ To filter only DNSBL replies containing 127.0.0.4:
+
+
+postscreen_dnsbl_sites = example.com=127.0.0.4
+
This feature is available in Postfix 2.8.
%PARAM postscreen_dnsbl_action continue
-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:
+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:
@@ -12642,10 +12687,11 @@ parameter. Specify one of the following:
- 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.
drop
@@ -12671,9 +12717,11 @@ postscreen_greet_wait parameter. Specify one of the following:
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.
+
drop
@@ -12726,8 +12774,9 @@ parameter. Specify one of the following:
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.
@@ -12942,17 +12991,17 @@ configuration parameter. See there for details.
%PARAM smtp_dns_resolver_options
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.
- res_defnames
-- 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.
- res_dnsrch
@@ -12971,3 +13020,10 @@ configuration parameter. See there for details.
This feature is available in Postfix 2.8 and later.
+%PARAM postscreen_dnsbl_threshold 1
+
+ The inclusive lower bound for blocking an SMTP client, based on
+its combined DNSBL score as defined with the postscreen_dnsbl_sites
+parameter.
+
+ This feature is available in Postfix 2.8.
diff --git a/postfix/src/dns/test_dns_lookup.c b/postfix/src/dns/test_dns_lookup.c
index 9771fc545..d7a80a482 100644
--- a/postfix/src/dns/test_dns_lookup.c
+++ b/postfix/src/dns/test_dns_lookup.c
@@ -99,7 +99,7 @@ int main(int argc, char **argv)
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));
diff --git a/postfix/src/dnsblog/dnsblog.c b/postfix/src/dnsblog/dnsblog.c
index 60a943e02..5cbfa161a 100644
--- a/postfix/src/dnsblog/dnsblog.c
+++ b/postfix/src/dnsblog/dnsblog.c
@@ -108,15 +108,18 @@ static VSTRING *rbl_domain;
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;
@@ -127,7 +130,6 @@ static int dnsblog_query(const char *dnsbl_domain, const char *addr)
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",
@@ -168,11 +170,11 @@ static int dnsblog_query(const char *dnsbl_domain, const char *addr)
}
/*
- * 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) {
@@ -181,7 +183,9 @@ static int dnsblog_query(const char *dnsbl_domain, const char *addr)
} 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);
@@ -193,7 +197,8 @@ static int dnsblog_query(const char *dnsbl_domain, const char *addr)
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 */
@@ -201,7 +206,6 @@ static int dnsblog_query(const char *dnsbl_domain, const char *addr)
static void dnsblog_service(VSTREAM *client_stream, char *unused_service,
char **argv)
{
- int found;
/*
* Sanity check. This service takes no command-line arguments.
@@ -217,13 +221,13 @@ static void dnsblog_service(VSTREAM *client_stream, char *unused_service,
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);
}
@@ -237,6 +241,7 @@ static void post_jail_init(char *unused_name, char **unused_argv)
addr = vstring_alloc(100);
query = vstring_alloc(100);
why = vstring_alloc(100);
+ result = vstring_alloc(100);
}
MAIL_VERSION_STAMP_DECLARE;
diff --git a/postfix/src/global/mail_params.h b/postfix/src/global/mail_params.h
index 200f34888..de9e6b8ea 100644
--- a/postfix/src/global/mail_params.h
+++ b/postfix/src/global/mail_params.h
@@ -3251,6 +3251,10 @@ extern char *var_ps_greet_action;
#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;
diff --git a/postfix/src/global/mail_proto.h b/postfix/src/global/mail_proto.h
index e4cd57440..c8b0e6a58 100644
--- a/postfix/src/global/mail_proto.h
+++ b/postfix/src/global/mail_proto.h
@@ -160,6 +160,7 @@ extern char *mail_pathname(const char *, const char *);
#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
diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h
index 3ee5e1529..e4906695f 100644
--- a/postfix/src/global/mail_version.h
+++ b/postfix/src/global/mail_version.h
@@ -20,7 +20,7 @@
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20100827"
+#define MAIL_RELEASE_DATE "20100829"
#define MAIL_VERSION_NUMBER "2.8"
#ifdef SNAPSHOT
diff --git a/postfix/src/postscreen/Makefile.in b/postfix/src/postscreen/Makefile.in
index dc3403e6c..ef605a5f7 100644
--- a/postfix/src/postscreen/Makefile.in
+++ b/postfix/src/postscreen/Makefile.in
@@ -1,6 +1,6 @@
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)
@@ -87,3 +87,40 @@ postscreen.o: ../../include/vbuf.h
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
diff --git a/postfix/src/postscreen/postscreen.c b/postfix/src/postscreen/postscreen.c
index 4cfabad6c..38d534b5d 100644
--- a/postfix/src/postscreen/postscreen.c
+++ b/postfix/src/postscreen/postscreen.c
@@ -191,20 +191,20 @@
/* 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
@@ -244,11 +244,17 @@
/* 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
@@ -285,7 +291,7 @@
/* .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
@@ -381,6 +387,10 @@
#include
+/* Application-specific. */
+
+#include
+
/*
* Configuration parameters.
*/
@@ -402,6 +412,7 @@ char *var_ps_wlist_nets;
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()
@@ -442,10 +453,6 @@ static DICT_CACHE *cache_map; /* cache table handle */
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 */
@@ -453,239 +460,6 @@ static ADDR_MATCH_LIST *wlist_nets; /* permanently whitelisted networks */
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,
@@ -842,7 +616,7 @@ static void send_socket(PS_STATE *state)
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 {
@@ -863,15 +637,15 @@ static void send_socket(PS_STATE *state)
}
}
-/* 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;
@@ -886,7 +660,7 @@ static void smtp_read_event(int event, char *context)
* 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
@@ -905,12 +679,12 @@ static void smtp_read_event(int event, char *context)
*/
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,
@@ -927,7 +701,7 @@ static void smtp_read_event(int event, char *context)
"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);
@@ -964,14 +738,14 @@ static void smtp_read_event(int event, char *context)
}
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;
@@ -1145,7 +919,7 @@ static void postscreen_service(VSTREAM *smtp_client_stream,
* 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);
@@ -1202,13 +976,13 @@ static void postscreen_service(VSTREAM *smtp_client_stream,
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 */
@@ -1315,10 +1089,7 @@ static void post_jail_init(char *unused_name, char **unused_argv)
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);
@@ -1371,6 +1142,7 @@ int main(int argc, char **argv)
};
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[] = {
diff --git a/postfix/src/postscreen/postscreen.h b/postfix/src/postscreen/postscreen.h
new file mode 100644
index 000000000..b834db986
--- /dev/null
+++ b/postfix/src/postscreen/postscreen.h
@@ -0,0 +1,92 @@
+/*++
+/* NAME
+/* postscreen 3h
+/* SUMMARY
+/* postscreen internal interfaces
+/* SYNOPSIS
+/* #include
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include
+
+ /*
+ * Global library.
+ */
+#include
+
+ /*
+ * 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
+/*--*/
diff --git a/postfix/src/postscreen/postscreen_dict.c b/postfix/src/postscreen/postscreen_dict.c
new file mode 100644
index 000000000..03a92b7e2
--- /dev/null
+++ b/postfix/src/postscreen/postscreen_dict.c
@@ -0,0 +1,107 @@
+/*++
+/* NAME
+/* postscreen_dict 3
+/* SUMMARY
+/* postscreen table access wrappers
+/* SYNOPSIS
+/* #include
+/*
+/* 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
+
+/* Utility library. */
+
+#include
+
+/* Application-specific. */
+
+#include
+
+ /*
+ * 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");
+}
diff --git a/postfix/src/postscreen/postscreen_dnsbl.c b/postfix/src/postscreen/postscreen_dnsbl.c
new file mode 100644
index 000000000..a39d9941f
--- /dev/null
+++ b/postfix/src/postscreen/postscreen_dnsbl.c
@@ -0,0 +1,382 @@
+/*++
+/* NAME
+/* postscreen_dnsbl 3
+/* SUMMARY
+/* postscreen DNSBL support
+/* SYNOPSIS
+/* #include
+/*
+/* 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
+#include /* sscanf */
+
+/* Utility library. */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* Global library. */
+
+#include
+#include
+
+/* Application-specific. */
+
+#include
+
+#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);
+}