From c78b3ad46635e0c45c3d78037d9c3891f8d45b37 Mon Sep 17 00:00:00 2001 From: Wietse Venema Date: Sun, 29 Aug 2010 00:00:00 -0500 Subject: [PATCH] postfix-2.8-20100830 --- postfix/.indent.pro | 4 +- postfix/HISTORY | 27 ++ postfix/RELEASE_NOTES | 40 +++ postfix/html/postconf.5.html | 107 ++++-- postfix/html/postscreen.8.html | 105 +++--- postfix/man/man5/postconf.5 | 102 ++++-- postfix/man/man8/postscreen.8 | 30 +- postfix/mantools/postlink | 1 + postfix/proto/postconf.proto | 102 ++++-- postfix/src/dns/test_dns_lookup.c | 2 +- postfix/src/dnsblog/dnsblog.c | 27 +- postfix/src/global/mail_params.h | 4 + postfix/src/global/mail_proto.h | 1 + postfix/src/global/mail_version.h | 2 +- postfix/src/postscreen/Makefile.in | 41 ++- postfix/src/postscreen/postscreen.c | 312 +++--------------- postfix/src/postscreen/postscreen.h | 92 ++++++ postfix/src/postscreen/postscreen_dict.c | 107 ++++++ postfix/src/postscreen/postscreen_dnsbl.c | 382 ++++++++++++++++++++++ 19 files changed, 1072 insertions(+), 416 deletions(-) create mode 100644 postfix/src/postscreen/postscreen.h create mode 100644 postfix/src/postscreen/postscreen_dict.c create mode 100644 postfix/src/postscreen/postscreen_dnsbl.c 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); +}