From: Wietse Venema Date: Fri, 26 Nov 2010 05:00:00 +0000 (-0500) Subject: postfix-2.8-20101126 X-Git-Tag: v2.8.0-RC1~15 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6ff328f8fbdfc1864b6bdb09f364d50108e51f2a;p=thirdparty%2Fpostfix.git postfix-2.8-20101126 --- diff --git a/postfix/HISTORY b/postfix/HISTORY index a7454217f..960ad72bd 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -15826,13 +15826,13 @@ Apologies for any names omitted. Bugfix (introduced Postfix 2.2): Postfix no longer appends the system default CA certificates to the lists specified with *_tls_CAfile or with *_tls_CApath. This prevents - third-party certificates from being trusted and given mail - relay permission with permit_tls_all_clientcerts. This - change may break valid configurations that do not use - permit_tls_all_clientcerts. To get the old behavior, specify - "tls_append_default_CA = yes". Files: tls/tls_certkey.c, - tls/tls_misc.c, global/mail_params.h. proto/postconf.proto, - mantools/postlink. + third-party certificates from getting mail relay permission + with the permit_tls_all_clientcerts feature. Unfortunately + this may cause compatibility problems with configurations + that rely on certificate verification for other purposes. + To get the old behavior, specify "tls_append_default_CA = + yes". Files: tls/tls_certkey.c, tls/tls_misc.c, + global/mail_params.h. proto/postconf.proto, mantools/postlink. 20100615 @@ -16136,3 +16136,43 @@ Apologies for any names omitted. DEFER_IF_REJECT when DNSWL lookup fails. The implementation is based on a design documented by Noel Jones (August 2010). File: smtpd/smtpd_check.c. + +20101108 + + Workaround: strip off IPv6 datalink suffix from peer address + to avoid problems with strict address checking code. Files: + smtpd/smtpd_peer.c, qmqpd/qmqpd_peer.c. + +20101114 + + Robustness: postscreen(8) now implements a time limit on + reading an entire command, instead of a time limit for + reading individual characters. File: postscreen/postscreen_smtpd.c. + +20101117 + + Bugfix: the "421" reply after Milter error was overruled + by Postfix 1.1 code that replied with "503" for RFC 2821 + compliance. We now make an exception for "final" replies, + as permitted by RFC. Solution by Victor Duchovni. File: + smtpd/smtpd.c. + +20101023 + + Cleanup: don't apply reject_rhsbl_helo to non-domain forms + such as network addresses. This would cause false positives + with dbl.spamhaus.org. File: smtpd/smtpd_check.c. + +20101124-6 + + Feature: pattern matching for DNSWL/DNSBL responses. For + example, with "reject_rbl_client example.com=d.d.d.d", each + "d" can now be a pattern inside "[]" that contains one or + more comma-separated decimal numbers or number..number + ranges. Files: smtpd/smtpd_check.c, postscreen/postscreen_dnsbl.c, + util/ip_match.c, util/ip_match.h. + +20101126 + + Cleanup: don't log "blocked using example.com=127.0.0.1", + just log the domain name. File: smtpd/smtpd_check.c. diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index 3f1a2cdb5..c22627260 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -33,6 +33,40 @@ This is supported only when the default value is stress-dependent postscreen parameters always evaluate as if the stress value is equal to the empty string. +Major changes with snapshot 20101126 +==================================== + +Support for address patterns in DNSBL and DNSWL lookup results. + +For example, "reject_rbl_client example.com = 127.0.0.[2,4,6..8]" +will reject clients when the lookup result is 127.0.0.2, 127.0.0.4, +127.0.0.6, 127.0.0.7, or 127.0.0.8. + +The setting "postscreen_dnsbl_sites = example.com=127.0.0.[2,4,6..8]" +rejects the same clients. + +An IPv4 address pattern has four fields separated by ".". Each +field is either a decimal number, or a sequence inside "[]" that +contains one or more comma-separated decimal numbers or number..number +ranges. + +Thus, any pattern field can be a sequence inside "[]", but a "[]" +sequence cannot span multiple address fields, and a pattern field +cannot contain both a number and a "[]" sequence at the same time. + +This means that the pattern 1.2.[3.4] is not valid (the sequence +[3.4] cannot span two address fields) and the pattern 1.2.3.3[6..9] +is also not valid (the last field cannot be both number 3 and +sequence [6..9] at the same time). + +The syntax for IPv4 patterns is as follows: + +v4pattern = v4field "." v4field "." v4field "." v4field +v4field = v4octet | "[" v4sequence "]" +v4octet = any decimal number in the range 0 through 255 +v4sequence = v4seq_member | v4sequence "," v4seq_member +v4seq_member = v4octet | v4octet ".." v4octet + Major changes with snapshot 20101105 ==================================== @@ -210,15 +244,15 @@ that sends mail into the after-filter SMTP server. Incompatibility with snapshot 20100610 ====================================== - + Postfix no longer appends the system-supplied default CA certificates to the lists specified with *_tls_CAfile or with *_tls_CApath. This -prevents third-party certificates from being trusted and given mail -relay permission with permit_tls_all_clientcerts. - -Unfortunately this change may break certificate verification on -sites that don't use permit_tls_all_clientcerts. Specify -"tls_append_default_CA = yes" for backwards compatibility. +prevents third-party certificates from getting mail relay permission +with the permit_tls_all_clientcerts feature. + +Unfortunately this change may cause compatibility problems when +configurations rely on certificate verification for other purposes. +Specify "tls_append_default_CA = yes" for backwards compatibility. Incompatibility with snapshot 20100101 ====================================== diff --git a/postfix/WISHLIST b/postfix/WISHLIST index 90490da14..a57d28a59 100644 --- a/postfix/WISHLIST +++ b/postfix/WISHLIST @@ -4,6 +4,17 @@ Wish list: anvil rate limit for sasl_username. + postscreen per-client connection count limit. + + smtpd xclient option for sasl_username. + + Documentation: add a note that smtpd_helo_required=yes is + needed to really enforce HELO restrictions. + + Use different ipc_timeout settings for email message + transactions (smtpd, pickup)->cleanup and for quick query/reply + transactions such as address rewriting/resolution. + permit_tempfail_action (default: defer_if_reject) to be used as the default value for dnswl_tempfail_action and rhswl_tempfail_action. Steal liberally from the code that diff --git a/postfix/html/postconf.5.html b/postfix/html/postconf.5.html index 66afd8452..c9d86854b 100644 --- a/postfix/html/postconf.5.html +++ b/postfix/html/postconf.5.html @@ -6802,7 +6802,8 @@ and error commands.

(default: ${stress?10}${stress:300}s)

The command "read" time limit for postscreen(8)'s built-in SMTP -protocol engine.

+protocol engine. This bounds the time to receive an entire command. +

This feature is available in Postfix 2.8.

@@ -11679,9 +11680,12 @@ Postfix version 2.5). This feature is available with Postfix version
Reject the request when the reversed client network address is listed with the A record "d.d.d.d" under rbl_domain -(Postfix version 2.1 and later only). If no "=d.d.d.d" is -specified, reject the request when the reversed client network -address is listed with any A record under rbl_domain.
+(Postfix version 2.1 and later only). Each "d" can be a +pattern inside "[]" that contains one or more comma-separated decimal +numbers or number..number ranges (Postfix version 2.8 and later). +If no "=d.d.d.d" is specified, reject the request when the +reversed client network address is listed with any A record under +rbl_domain.
The maps_rbl_reject_code parameter specifies the response code for rejected requests (default: 554), the default_rbl_reply parameter specifies the default server reply, and the rbl_reply_maps parameter @@ -11692,6 +11696,8 @@ This feature is available in Postfix 2.0 and later.
Accept the request when the reversed client network address is listed with the A record "d.d.d.d" under dnswl_domain. +Each "d" can be a pattern inside "[]" that contains one or +more comma-separated decimal numbers or number..number ranges. If no "=d.d.d.d" is specified, accept the request when the reversed client network address is listed with any A record under dnswl_domain.
For safety, permit_dnswl_client is silently @@ -11699,12 +11705,15 @@ ignored when it would override reject_rhsbl_client rbl_domain=d.d.d.d +
reject_rhsbl_client rbl_domain=d.d.d.d
Reject the request when the client hostname is listed with the A record "d.d.d.d" under rbl_domain (Postfix version -2.1 and later only). If no "=d.d.d.d" is specified, reject -the request when the client hostname is listed with +2.1 and later only). Each "d" can be a pattern inside "[]" +that contains one or more comma-separated decimal numbers or +number..number ranges (Postfix version 2.8 and later). If no +"=d.d.d.d" is specified, reject the request when the client +hostname is listed with any A record under rbl_domain. See the reject_rbl_client description above for additional RBL related configuration parameters. This feature is available in Postfix 2.0 and later; with Postfix @@ -11714,7 +11723,9 @@ produce better results.
permit_rhswl_client rhswl_domain=d.d.d.d
Accept the request when the client hostname is listed with the -A record "d.d.d.d" under rhswl_domain. If no +A record "d.d.d.d" under rhswl_domain. Each "d" +can be a pattern inside "[]" that contains one or more comma-separated +decimal numbers or number..number ranges. If no "=d.d.d.d" is specified, accept the request when the client hostname is listed with any A record under rhswl_domain.
Caution: client name whitelisting is fragile, since the client @@ -11730,6 +11741,8 @@ when whitelist lookup fails. This feature is available in Postfix
Reject the request when the unverified reverse client hostname is listed with the A record "d.d.d.d" under rbl_domain. +Each "d" can be a pattern inside "[]" that contains one or +more comma-separated decimal numbers or number..number ranges. If no "=d.d.d.d" is specified, reject the request when the unverified reverse client hostname is listed with any A record under rbl_domain. See the reject_rbl_client description above for @@ -12368,7 +12381,10 @@ rejected requests (default: 504).
Reject the request when the HELO or EHLO hostname hostname is listed with the A record "d.d.d.d" under rbl_domain -(Postfix version 2.1 and later only). If no "=d.d.d.d" is +(Postfix version 2.1 and later only). Each "d" can be a +pattern inside "[]" that contains one or more comma-separated decimal +numbers or number..number ranges (Postfix version 2.8 and later). +If no "=d.d.d.d" is specified, reject the request when the HELO or EHLO hostname is listed with any A record under rbl_domain. See the reject_rbl_client description for additional RBL related configuration @@ -12784,7 +12800,10 @@ rejected requests (default: 504).
Reject the request when the RCPT TO domain is listed with the A record "d.d.d.d" under rbl_domain (Postfix version -2.1 and later only). If no "=d.d.d.d" is specified, reject +2.1 and later only). Each "d" can be a pattern inside "[]" +that contains one or more comma-separated decimal numbers or +number..number ranges (Postfix version 2.8 and later). If no +"=d.d.d.d" is specified, reject the request when the RCPT TO domain is listed with any A record under rbl_domain.
The maps_rbl_reject_code parameter specifies the response code for rejected requests (default: @@ -13328,7 +13347,10 @@ rejected requests (default: 504).
Reject the request when the MAIL FROM domain is listed with the A record "d.d.d.d" under rbl_domain (Postfix -version 2.1 and later only). If no "=d.d.d.d" is specified, +version 2.1 and later only). Each "d" can be a pattern +inside "[]" that contains one or more comma-separated decimal numbers +or number..number ranges (Postfix version 2.8 and later). If no +"=d.d.d.d" is specified, reject the request when the MAIL FROM domain is listed with any A record under rbl_domain.
The maps_rbl_reject_code parameter specifies the response code for diff --git a/postfix/man/man5/postconf.5 b/postfix/man/man5/postconf.5 index db80efbe1..3a98c78b8 100644 --- a/postfix/man/man5/postconf.5 +++ b/postfix/man/man5/postconf.5 @@ -3828,7 +3828,7 @@ and error commands. This feature is available in Postfix 2.8. .SH postscreen_command_time_limit (default: ${stress?10}${stress:300}s) The command "read" time limit for \fBpostscreen\fR(8)'s built-in SMTP -protocol engine. +protocol engine. This bounds the time to receive an entire command. .PP This feature is available in Postfix 2.8. .SH postscreen_disable_vrfy_command (default: $disable_vrfy_command) @@ -7166,9 +7166,12 @@ Postfix version 2.5). This feature is available with Postfix version .IP "\fBreject_rbl_client \fIrbl_domain=d.d.d.d\fR\fR" Reject the request when the reversed client network address is listed with the A record "\fId.d.d.d\fR" under \fIrbl_domain\fR -(Postfix version 2.1 and later only). If no "\fI=d.d.d.d\fR" is -specified, reject the request when the reversed client network -address is listed with any A record under \fIrbl_domain\fR. +(Postfix version 2.1 and later only). Each "\fId\fR" can be a +pattern inside "[]" that contains one or more comma-separated decimal +numbers or number..number ranges (Postfix version 2.8 and later). +If no "\fI=d.d.d.d\fR" is specified, reject the request when the +reversed client network address is listed with any A record under +\fIrbl_domain\fR. .br The maps_rbl_reject_code parameter specifies the response code for rejected requests (default: 554), the default_rbl_reply parameter @@ -7178,6 +7181,8 @@ This feature is available in Postfix 2.0 and later. .IP "\fBpermit_dnswl_client \fIdnswl_domain=d.d.d.d\fR\fR" Accept the request when the reversed client network address is listed with the A record "\fId.d.d.d\fR" under \fIdnswl_domain\fR. +Each "\fId\fR" can be a pattern inside "[]" that contains one or +more comma-separated decimal numbers or number..number ranges. If no "\fI=d.d.d.d\fR" is specified, accept the request when the reversed client network address is listed with any A record under \fIdnswl_domain\fR. @@ -7189,8 +7194,11 @@ is available in Postfix 2.8 and later. .IP "\fBreject_rhsbl_client \fIrbl_domain=d.d.d.d\fR\fR" Reject the request when the client hostname is listed with the A record "\fId.d.d.d\fR" under \fIrbl_domain\fR (Postfix version -2.1 and later only). If no "\fI=d.d.d.d\fR" is specified, reject -the request when the client hostname is listed with +2.1 and later only). Each "\fId\fR" can be a pattern inside "[]" +that contains one or more comma-separated decimal numbers or +number..number ranges (Postfix version 2.8 and later). If no +"\fI=d.d.d.d\fR" is specified, reject the request when the client +hostname is listed with any A record under \fIrbl_domain\fR. See the reject_rbl_client description above for additional RBL related configuration parameters. This feature is available in Postfix 2.0 and later; with Postfix @@ -7198,7 +7206,9 @@ version 2.8 and later, reject_rhsbl_reverse_client will usually produce better results. .IP "\fBpermit_rhswl_client \fIrhswl_domain=d.d.d.d\fR\fR" Accept the request when the client hostname is listed with the -A record "\fId.d.d.d\fR" under \fIrhswl_domain\fR. If no +A record "\fId.d.d.d\fR" under \fIrhswl_domain\fR. Each "\fId\fR" +can be a pattern inside "[]" that contains one or more comma-separated +decimal numbers or number..number ranges. If no "\fI=d.d.d.d\fR" is specified, accept the request when the client hostname is listed with any A record under \fIrhswl_domain\fR. .br @@ -7214,6 +7224,8 @@ when whitelist lookup fails. This feature is available in Postfix .IP "\fBreject_rhsbl_reverse_client \fIrbl_domain=d.d.d.d\fR\fR" Reject the request when the unverified reverse client hostname is listed with the A record "\fId.d.d.d\fR" under \fIrbl_domain\fR. +Each "\fId\fR" can be a pattern inside "[]" that contains one or +more comma-separated decimal numbers or number..number ranges. If no "\fI=d.d.d.d\fR" is specified, reject the request when the unverified reverse client hostname is listed with any A record under \fIrbl_domain\fR. See the reject_rbl_client description above for @@ -7682,7 +7694,10 @@ rejected requests (default: 504). .IP "\fBreject_rhsbl_helo \fIrbl_domain=d.d.d.d\fR\fR" Reject the request when the HELO or EHLO hostname hostname is listed with the A record "\fId.d.d.d\fR" under \fIrbl_domain\fR -(Postfix version 2.1 and later only). If no "\fI=d.d.d.d\fR" is +(Postfix version 2.1 and later only). Each "\fId\fR" can be a +pattern inside "[]" that contains one or more comma-separated decimal +numbers or number..number ranges (Postfix version 2.8 and later). +If no "\fI=d.d.d.d\fR" is specified, reject the request when the HELO or EHLO hostname is listed with any A record under \fIrbl_domain\fR. See the reject_rbl_client description for additional RBL related configuration @@ -7924,7 +7939,10 @@ rejected requests (default: 504). .IP "\fBreject_rhsbl_recipient \fIrbl_domain=d.d.d.d\fR\fR" Reject the request when the RCPT TO domain is listed with the A record "\fId.d.d.d\fR" under \fIrbl_domain\fR (Postfix version -2.1 and later only). If no "\fI=d.d.d.d\fR" is specified, reject +2.1 and later only). Each "\fId\fR" can be a pattern inside "[]" +that contains one or more comma-separated decimal numbers or +number..number ranges (Postfix version 2.8 and later). If no +"\fI=d.d.d.d\fR" is specified, reject the request when the RCPT TO domain is listed with any A record under \fIrbl_domain\fR. .br @@ -8294,7 +8312,10 @@ rejected requests (default: 504). .IP "\fBreject_rhsbl_sender \fIrbl_domain=d.d.d.d\fR\fR" Reject the request when the MAIL FROM domain is listed with the A record "\fId.d.d.d\fR" under \fIrbl_domain\fR (Postfix -version 2.1 and later only). If no "\fI=d.d.d.d\fR" is specified, +version 2.1 and later only). Each "\fId\fR" can be a pattern +inside "[]" that contains one or more comma-separated decimal numbers +or number..number ranges (Postfix version 2.8 and later). If no +"\fI=d.d.d.d\fR" is specified, reject the request when the MAIL FROM domain is listed with any A record under \fIrbl_domain\fR. .br diff --git a/postfix/proto/postconf.proto b/postfix/proto/postconf.proto index 6e3ce4e1e..16d9d3942 100644 --- a/postfix/proto/postconf.proto +++ b/postfix/proto/postconf.proto @@ -4879,9 +4879,12 @@ Postfix version 2.5). This feature is available with Postfix version
Reject the request when the reversed client network address is listed with the A record "d.d.d.d" under rbl_domain -(Postfix version 2.1 and later only). If no "=d.d.d.d" is -specified, reject the request when the reversed client network -address is listed with any A record under rbl_domain.
+(Postfix version 2.1 and later only). Each "d" can be a +pattern inside "[]" that contains one or more comma-separated decimal +numbers or number..number ranges (Postfix version 2.8 and later). +If no "=d.d.d.d" is specified, reject the request when the +reversed client network address is listed with any A record under +rbl_domain.
The maps_rbl_reject_code parameter specifies the response code for rejected requests (default: 554), the default_rbl_reply parameter specifies the default server reply, and the rbl_reply_maps parameter @@ -4892,6 +4895,8 @@ This feature is available in Postfix 2.0 and later.
Accept the request when the reversed client network address is listed with the A record "d.d.d.d" under dnswl_domain. +Each "d" can be a pattern inside "[]" that contains one or +more comma-separated decimal numbers or number..number ranges. If no "=d.d.d.d" is specified, accept the request when the reversed client network address is listed with any A record under dnswl_domain.
For safety, permit_dnswl_client is silently @@ -4903,8 +4908,11 @@ is available in Postfix 2.8 and later.
Reject the request when the client hostname is listed with the A record "d.d.d.d" under rbl_domain (Postfix version -2.1 and later only). If no "=d.d.d.d" is specified, reject -the request when the client hostname is listed with +2.1 and later only). Each "d" can be a pattern inside "[]" +that contains one or more comma-separated decimal numbers or +number..number ranges (Postfix version 2.8 and later). If no +"=d.d.d.d" is specified, reject the request when the client +hostname is listed with any A record under rbl_domain. See the reject_rbl_client description above for additional RBL related configuration parameters. This feature is available in Postfix 2.0 and later; with Postfix @@ -4914,7 +4922,9 @@ produce better results.
permit_rhswl_client rhswl_domain=d.d.d.d
Accept the request when the client hostname is listed with the -A record "d.d.d.d" under rhswl_domain. If no +A record "d.d.d.d" under rhswl_domain. Each "d" +can be a pattern inside "[]" that contains one or more comma-separated +decimal numbers or number..number ranges. If no "=d.d.d.d" is specified, accept the request when the client hostname is listed with any A record under rhswl_domain.
Caution: client name whitelisting is fragile, since the client @@ -4930,6 +4940,8 @@ when whitelist lookup fails. This feature is available in Postfix
Reject the request when the unverified reverse client hostname is listed with the A record "d.d.d.d" under rbl_domain. +Each "d" can be a pattern inside "[]" that contains one or +more comma-separated decimal numbers or number..number ranges. If no "=d.d.d.d" is specified, reject the request when the unverified reverse client hostname is listed with any A record under rbl_domain. See the reject_rbl_client description above for @@ -5369,7 +5381,10 @@ rejected requests (default: 504).
Reject the request when the HELO or EHLO hostname hostname is listed with the A record "d.d.d.d" under rbl_domain -(Postfix version 2.1 and later only). If no "=d.d.d.d" is +(Postfix version 2.1 and later only). Each "d" can be a +pattern inside "[]" that contains one or more comma-separated decimal +numbers or number..number ranges (Postfix version 2.8 and later). +If no "=d.d.d.d" is specified, reject the request when the HELO or EHLO hostname is listed with any A record under rbl_domain. See the reject_rbl_client description for additional RBL related configuration @@ -5659,7 +5674,10 @@ rejected requests (default: 504).
Reject the request when the RCPT TO domain is listed with the A record "d.d.d.d" under rbl_domain (Postfix version -2.1 and later only). If no "=d.d.d.d" is specified, reject +2.1 and later only). Each "d" can be a pattern inside "[]" +that contains one or more comma-separated decimal numbers or +number..number ranges (Postfix version 2.8 and later). If no +"=d.d.d.d" is specified, reject the request when the RCPT TO domain is listed with any A record under rbl_domain.
The maps_rbl_reject_code parameter specifies the response code for rejected requests (default: @@ -6034,7 +6052,10 @@ rejected requests (default: 504).
Reject the request when the MAIL FROM domain is listed with the A record "d.d.d.d" under rbl_domain (Postfix -version 2.1 and later only). If no "=d.d.d.d" is specified, +version 2.1 and later only). Each "d" can be a pattern +inside "[]" that contains one or more comma-separated decimal numbers +or number..number ranges (Postfix version 2.8 and later). If no +"=d.d.d.d" is specified, reject the request when the MAIL FROM domain is listed with any A record under rbl_domain.
The maps_rbl_reject_code parameter specifies the response code for @@ -13062,7 +13083,8 @@ and error commands.

%PARAM postscreen_command_time_limit ${stress?10}${stress:300}s

The command "read" time limit for postscreen(8)'s built-in SMTP -protocol engine.

+protocol engine. This bounds the time to receive an entire command. +

This feature is available in Postfix 2.8.

diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index d7e5eb580..a5e6a34bb 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 "20101108" +#define MAIL_RELEASE_DATE "20101126" #define MAIL_VERSION_NUMBER "2.8" #ifdef SNAPSHOT diff --git a/postfix/src/postscreen/Makefile.in b/postfix/src/postscreen/Makefile.in index 3b35045d2..6957030b2 100644 --- a/postfix/src/postscreen/Makefile.in +++ b/postfix/src/postscreen/Makefile.in @@ -81,6 +81,7 @@ postscreen.o: ../../include/myaddrinfo.h postscreen.o: ../../include/mymalloc.h postscreen.o: ../../include/name_code.h postscreen.o: ../../include/set_eugid.h +postscreen.o: ../../include/string_list.h postscreen.o: ../../include/sys_defs.h postscreen.o: ../../include/vbuf.h postscreen.o: ../../include/vstream.h @@ -95,6 +96,7 @@ postscreen_dict.o: ../../include/events.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/string_list.h postscreen_dict.o: ../../include/sys_defs.h postscreen_dict.o: ../../include/vbuf.h postscreen_dict.o: ../../include/vstream.h @@ -110,6 +112,7 @@ 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/ip_match.h postscreen_dnsbl.o: ../../include/mail_params.h postscreen_dnsbl.o: ../../include/mail_proto.h postscreen_dnsbl.o: ../../include/match_list.h @@ -117,6 +120,7 @@ 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/string_list.h postscreen_dnsbl.o: ../../include/sys_defs.h postscreen_dnsbl.o: ../../include/valid_hostname.h postscreen_dnsbl.o: ../../include/vbuf.h @@ -134,6 +138,7 @@ postscreen_early.o: ../../include/match_list.h postscreen_early.o: ../../include/match_ops.h postscreen_early.o: ../../include/msg.h postscreen_early.o: ../../include/mymalloc.h +postscreen_early.o: ../../include/string_list.h postscreen_early.o: ../../include/stringops.h postscreen_early.o: ../../include/sys_defs.h postscreen_early.o: ../../include/vbuf.h @@ -152,6 +157,7 @@ postscreen_misc.o: ../../include/mail_params.h postscreen_misc.o: ../../include/match_list.h postscreen_misc.o: ../../include/match_ops.h postscreen_misc.o: ../../include/msg.h +postscreen_misc.o: ../../include/string_list.h postscreen_misc.o: ../../include/sys_defs.h postscreen_misc.o: ../../include/vbuf.h postscreen_misc.o: ../../include/vstream.h @@ -168,6 +174,7 @@ postscreen_send.o: ../../include/iostuff.h postscreen_send.o: ../../include/match_list.h postscreen_send.o: ../../include/match_ops.h postscreen_send.o: ../../include/msg.h +postscreen_send.o: ../../include/string_list.h postscreen_send.o: ../../include/sys_defs.h postscreen_send.o: ../../include/vbuf.h postscreen_send.o: ../../include/vstream.h @@ -181,12 +188,14 @@ postscreen_smtpd.o: ../../include/dict.h postscreen_smtpd.o: ../../include/dict_cache.h postscreen_smtpd.o: ../../include/events.h postscreen_smtpd.o: ../../include/iostuff.h +postscreen_smtpd.o: ../../include/is_header.h postscreen_smtpd.o: ../../include/mail_params.h postscreen_smtpd.o: ../../include/mail_proto.h postscreen_smtpd.o: ../../include/match_list.h postscreen_smtpd.o: ../../include/match_ops.h postscreen_smtpd.o: ../../include/msg.h postscreen_smtpd.o: ../../include/mymalloc.h +postscreen_smtpd.o: ../../include/string_list.h postscreen_smtpd.o: ../../include/stringops.h postscreen_smtpd.o: ../../include/sys_defs.h postscreen_smtpd.o: ../../include/vbuf.h @@ -208,6 +217,7 @@ postscreen_state.o: ../../include/match_ops.h postscreen_state.o: ../../include/msg.h postscreen_state.o: ../../include/mymalloc.h postscreen_state.o: ../../include/name_mask.h +postscreen_state.o: ../../include/string_list.h postscreen_state.o: ../../include/sys_defs.h postscreen_state.o: ../../include/vbuf.h postscreen_state.o: ../../include/vstream.h @@ -223,6 +233,7 @@ postscreen_tests.o: ../../include/mail_params.h postscreen_tests.o: ../../include/match_list.h postscreen_tests.o: ../../include/match_ops.h postscreen_tests.o: ../../include/msg.h +postscreen_tests.o: ../../include/string_list.h postscreen_tests.o: ../../include/sys_defs.h postscreen_tests.o: ../../include/vbuf.h postscreen_tests.o: ../../include/vstream.h diff --git a/postfix/src/postscreen/postscreen_dnsbl.c b/postfix/src/postscreen/postscreen_dnsbl.c index 289a38bf1..d28c90e42 100644 --- a/postfix/src/postscreen/postscreen_dnsbl.c +++ b/postfix/src/postscreen/postscreen_dnsbl.c @@ -54,6 +54,9 @@ /* System library. */ #include +#include /* AF_INET */ +#include /* inet_pton() */ +#include /* inet_pton() */ #include /* sscanf */ /* Utility library. */ @@ -67,6 +70,8 @@ #include #include #include +#include +#include /* Global library. */ @@ -199,13 +204,15 @@ static void ps_dnsbl_add_site(const char *site) { const char *myname = "ps_dnsbl_add_site"; char *saved_site = mystrdup(site); + VSTRING *byte_codes = 0; PS_DNSBL_HEAD *head; PS_DNSBL_SITE *new_site; char junk; const char *weight_text; - const char *pattern_text; + char *pattern_text; int weight; HTABLE_INFO *ht; + char *parse_err; /* * Parse the required DNSBL domain name, the optional reply filter and @@ -221,11 +228,11 @@ static void ps_dnsbl_add_site(const char *site) } else { weight = 1; } - /* Preliminary fixed-string filter. */ + /* Reply 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); + byte_codes = vstring_alloc(100); + if ((parse_err = ip_match_parse(byte_codes, pattern_text)) != 0) + msg_fatal("bad DNSBL filter syntax: %s", parse_err); } if (valid_hostname(saved_site, DO_GRIPE) == 0) msg_fatal("bad DNSBL domain name \"%s\" in \"%s\"", @@ -256,26 +263,33 @@ static void ps_dnsbl_add_site(const char *site) * name. */ new_site = (PS_DNSBL_SITE *) mymalloc(sizeof(*new_site)); - new_site->filter = (pattern_text ? mystrdup(pattern_text) : 0); + new_site->filter = (pattern_text ? ip_match_save(byte_codes) : 0); new_site->weight = weight; new_site->next = head->first; head->first = new_site; myfree(saved_site); + if (byte_codes) + vstring_free(byte_codes); } /* ps_dnsbl_match - match DNSBL reply filter */ static int ps_dnsbl_match(const char *filter, ARGV *reply) { + char addr_buf[MAI_HOSTADDR_STRSIZE]; char **cpp; /* - * Preliminary fixed-string implementation. + * Run the replies through the pattern-matching engine. */ - for (cpp = reply->argv; *cpp != 0; cpp++) - if (strcmp(filter, *cpp) == 0) + for (cpp = reply->argv; *cpp != 0; cpp++) { + if (inet_pton(AF_INET, *cpp, addr_buf) != 1) + msg_warn("address conversion error for %s -- ignoring this reply", + *cpp); + if (ip_match_execute(filter, addr_buf)) return (1); + } return (0); } diff --git a/postfix/src/postscreen/postscreen_smtpd.c b/postfix/src/postscreen/postscreen_smtpd.c index 2e4af5022..f3d6c4267 100644 --- a/postfix/src/postscreen/postscreen_smtpd.c +++ b/postfix/src/postscreen/postscreen_smtpd.c @@ -350,6 +350,8 @@ static int ps_data_cmd(PS_STATE *state, char *args) * so that we dont't have to deal with broken zombies that fall silent at * the first reject response. For now we rely on stress-dependent command * read timeouts. + * + * If we proceed into the data phase, enforce over-all DATA time limit. */ return (PS_SEND_REPLY(state, "554 5.5.1 Error: no valid recipients\r\n")); @@ -614,15 +616,14 @@ static void ps_smtpd_read_event(int event, char *context) } /* - * Yield this pseudo thread when the VSTREAM buffer is empty. + * Yield this pseudo thread when the VSTREAM buffer is empty in + * the middle of a command. * - * Set a short per-command timeout if under stress. + * XXX Do not reset the read timeout. The entire command must be + * received within the time limit. */ - if (PS_SMTPD_BUFFER_EMPTY(state)) { - event_request_timer(ps_smtpd_time_event, (char *) state, - PS_EFF_CMD_TIME_LIMIT); + if (PS_SMTPD_BUFFER_EMPTY(state)) return; - } } /* @@ -788,16 +789,17 @@ static void ps_smtpd_read_event(int event, char *context) return; } + /* + * Reset the command read timeout before reading the next command. + */ + event_request_timer(ps_smtpd_time_event, (char *) state, + PS_EFF_CMD_TIME_LIMIT); + /* * Yield this pseudo thread when the VSTREAM buffer is empty. - * - * Set a short per-command timeout if under stress. */ - if (PS_SMTPD_BUFFER_EMPTY(state)) { - event_request_timer(ps_smtpd_time_event, (char *) state, - PS_EFF_CMD_TIME_LIMIT); + if (PS_SMTPD_BUFFER_EMPTY(state)) return; - } } } diff --git a/postfix/src/postscreen/postscreen_tests.c b/postfix/src/postscreen/postscreen_tests.c index 7c7fdfe0b..a281d4a50 100644 --- a/postfix/src/postscreen/postscreen_tests.c +++ b/postfix/src/postscreen/postscreen_tests.c @@ -163,8 +163,12 @@ void ps_parse_tests(PS_STATE *state, unsigned long nsmtp_stamp; unsigned long barlf_stamp; unsigned long penal_stamp; + +#ifdef NONPROD time_t penalty_left; +#endif + /* * We don't know what tests have expired or have never passed. */ diff --git a/postfix/src/qmqpd/Makefile.in b/postfix/src/qmqpd/Makefile.in index ef50fda86..44bb07109 100644 --- a/postfix/src/qmqpd/Makefile.in +++ b/postfix/src/qmqpd/Makefile.in @@ -83,6 +83,7 @@ qmqpd.o: ../../include/netstring.h qmqpd.o: ../../include/quote_822_local.h qmqpd.o: ../../include/quote_flags.h qmqpd.o: ../../include/rec_type.h +qmqpd.o: ../../include/recipient_list.h qmqpd.o: ../../include/record.h qmqpd.o: ../../include/sys_defs.h qmqpd.o: ../../include/vbuf.h @@ -101,6 +102,7 @@ qmqpd_peer.o: ../../include/msg.h qmqpd_peer.o: ../../include/myaddrinfo.h qmqpd_peer.o: ../../include/mymalloc.h qmqpd_peer.o: ../../include/sock_addr.h +qmqpd_peer.o: ../../include/split_at.h qmqpd_peer.o: ../../include/stringops.h qmqpd_peer.o: ../../include/sys_defs.h qmqpd_peer.o: ../../include/valid_hostname.h diff --git a/postfix/src/qmqpd/qmqpd_peer.c b/postfix/src/qmqpd/qmqpd_peer.c index f026c9e86..a4bfe5fe7 100644 --- a/postfix/src/qmqpd/qmqpd_peer.c +++ b/postfix/src/qmqpd/qmqpd_peer.c @@ -57,6 +57,7 @@ #include #include #include +#include /* Global library. */ @@ -150,6 +151,14 @@ void qmqpd_peer_init(QMQPD_STATE *state) myname, MAI_STRERROR(aierr)); state->port = mystrdup(client_port.buf); + /* + * XXX Strip off the IPv6 datalink suffix to avoid false alarms with + * strict address syntax checks. + */ +#ifdef HAS_IPV6 + (void) split_at(client_addr.buf, '%'); +#endif + /* * We convert IPv4-in-IPv6 address to 'true' IPv4 address early on, * but only if IPv4 support is enabled (why would anyone want to turn diff --git a/postfix/src/smtpd/Makefile.in b/postfix/src/smtpd/Makefile.in index 01bd2ef40..0273877d6 100644 --- a/postfix/src/smtpd/Makefile.in +++ b/postfix/src/smtpd/Makefile.in @@ -255,6 +255,7 @@ smtpd_check.o: ../../include/inet_addr_list.h smtpd_check.o: ../../include/inet_proto.h smtpd_check.o: ../../include/input_transp.h smtpd_check.o: ../../include/iostuff.h +smtpd_check.o: ../../include/ip_match.h smtpd_check.o: ../../include/is_header.h smtpd_check.o: ../../include/mac_expand.h smtpd_check.o: ../../include/mac_parse.h @@ -344,6 +345,7 @@ smtpd_peer.o: ../../include/mymalloc.h smtpd_peer.o: ../../include/name_code.h smtpd_peer.o: ../../include/name_mask.h smtpd_peer.o: ../../include/sock_addr.h +smtpd_peer.o: ../../include/split_at.h smtpd_peer.o: ../../include/stringops.h smtpd_peer.o: ../../include/sys_defs.h smtpd_peer.o: ../../include/tls.h diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c index 5e074a509..8adc1f1a3 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -3044,6 +3044,9 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) * XXX Should use something other than CLEANUP_STAT_WRITE when we lose * contact with the cleanup server. This requires changes to the * mail_stream module and its users (smtpd, qmqpd, perhaps sendmail). + * + * XXX See exception below in code that overrides state->access_denied for + * compliance with RFC 2821 Sec 3.1. */ if (smtpd_milters != 0 && (state->err & CLEANUP_STAT_WRITE) != 0) state->access_denied = mystrdup("421 4.3.0 Mail system error"); @@ -4521,6 +4524,11 @@ static void smtpd_proto(SMTPD_STATE *state) } /* XXX We use the real client for connect access control. */ if (state->access_denied && cmdp->action != quit_cmd) { + /* XXX Exception for Milter override. */ + if (strncmp(state->access_denied + 1, "21", 2) == 0) { + smtpd_chat_reply(state, "%s", state->access_denied); + continue; + } smtpd_chat_reply(state, "503 5.7.0 Error: access denied for %s", state->namaddr); /* RFC 2821 Sec 3.1 */ state->error_count++; diff --git a/postfix/src/smtpd/smtpd_check.c b/postfix/src/smtpd/smtpd_check.c index 7bfc04335..c1b2330bf 100644 --- a/postfix/src/smtpd/smtpd_check.c +++ b/postfix/src/smtpd/smtpd_check.c @@ -203,6 +203,7 @@ #include #include #include +#include /* DNS library. */ @@ -266,6 +267,7 @@ static jmp_buf smtpd_check_buf; */ static VSTRING *error_text; static CTABLE *smtpd_rbl_cache; +static CTABLE *smtpd_rbl_byte_cache; /* * Pre-opened SMTP recipient maps so we can reject mail for unknown users. @@ -418,18 +420,20 @@ static int PRINTFLIKE(5, 6) smtpd_check_reject(SMTPD_STATE *, int, int, const ch */ typedef struct { char *txt; /* TXT content or NULL */ - ARGV *a; /* A records */ + DNS_RR *a; /* A records */ } SMTPD_RBL_STATE; static void *rbl_pagein(const char *, void *); static void rbl_pageout(void *, void *); +static void *rbl_byte_pagein(const char *, void *); +static void rbl_byte_pageout(void *, void *); /* * Context for RBL $name expansion. */ typedef struct { SMTPD_STATE *state; /* general state */ - const char *domain; /* query domain */ + char *domain; /* query domain */ const char *what; /* rejected value */ const char *class; /* name of rejected value */ const char *txt; /* randomly selected trimmed TXT rr */ @@ -639,6 +643,8 @@ void smtpd_check_init(void) * sessions so we cannot make it dependent on session state. */ smtpd_rbl_cache = ctable_create(100, rbl_pagein, rbl_pageout, (void *) 0); + smtpd_rbl_byte_cache = ctable_create(1000, rbl_byte_pagein, + rbl_byte_pageout, (void *) 0); /* * Pre-parse the restriction lists. At the same time, pre-open tables @@ -2952,13 +2958,11 @@ static SMTPD_RBL_STATE dnsxl_stat_soft[1]; static void *rbl_pagein(const char *query, void *unused_context) { - const char *myname = "rbl_pagein"; DNS_RR *txt_list; VSTRING *why; int dns_status; SMTPD_RBL_STATE *rbl = 0; DNS_RR *addr_list; - MAI_HOSTADDR_STR hostaddr; DNS_RR *rr; DNS_RR *next; VSTRING *buf; @@ -3006,15 +3010,7 @@ static void *rbl_pagein(const char *query, void *unused_context) dns_rr_free(txt_list); } else rbl->txt = 0; - rbl->a = argv_alloc(1); - for (rr = addr_list; rr != 0; rr = rr->next) { - if (dns_rr_to_pa(rr, &hostaddr) == 0) - msg_warn("%s: skipping record type %s for query %s: %m", - myname, dns_strtype(rr->type), query); - else - argv_add(rbl->a, hostaddr.buf, ARGV_END); - } - dns_rr_free(addr_list); + rbl->a = addr_list; return ((void *) rbl); } @@ -3028,11 +3024,35 @@ static void rbl_pageout(void *data, void *unused_context) if (rbl->txt) myfree(rbl->txt); if (rbl->a) - argv_free(rbl->a); + dns_rr_free(rbl->a); myfree((char *) rbl); } } +/* rbl_byte_pagein - parse RBL reply pattern, save byte codes */ + +static void *rbl_byte_pagein(const char *query, void *unused_context) +{ + VSTRING *byte_codes = vstring_alloc(100); + char *saved_query = mystrdup(query); + char *saved_byte_codes; + char *err; + + if ((err = ip_match_parse(byte_codes, saved_query)) != 0) + msg_fatal("RBL reply error: %s", err); + saved_byte_codes = ip_match_save(byte_codes); + myfree(saved_query); + vstring_free(byte_codes); + return (saved_byte_codes); +} + +/* rbl_byte_pageout - discard parsed RBL reply byte codes */ + +static void rbl_byte_pageout(void *data, void *unused_context) +{ + myfree(data); +} + /* rbl_expand_lookup - RBL specific $name expansion */ static const char *rbl_expand_lookup(const char *name, int mode, @@ -3091,7 +3111,8 @@ static int rbl_reject_reply(SMTPD_STATE *state, const SMTPD_RBL_STATE *rbl, } why = vstring_alloc(100); rbl_exp.state = state; - rbl_exp.domain = rbl_domain; + rbl_exp.domain = mystrdup(rbl_domain); + (void) split_at(rbl_exp.domain, '='); rbl_exp.what = what; rbl_exp.class = reply_class; rbl_exp.txt = (rbl->txt == 0 ? "" : rbl->txt); @@ -3138,6 +3159,7 @@ static int rbl_reject_reply(SMTPD_STATE *state, const SMTPD_RBL_STATE *rbl, /* * Clean up. */ + myfree(rbl_exp.domain); vstring_free(why); return (result); @@ -3145,13 +3167,20 @@ static int rbl_reject_reply(SMTPD_STATE *state, const SMTPD_RBL_STATE *rbl, /* rbl_match_addr - match address list */ -static int rbl_match_addr(SMTPD_RBL_STATE *rbl, const char *addr) +static int rbl_match_addr(SMTPD_RBL_STATE *rbl, const char *byte_codes) { - char **cpp; + const char *myname = "rbl_match_addr"; + DNS_RR *rr; - for (cpp = rbl->a->argv; *cpp; cpp++) - if (strcmp(*cpp, addr) == 0) - return (1); + for (rr = rbl->a; rr != 0; rr = rr->next) { + if (rr->type == T_A) { + if (ip_match_execute(byte_codes, rr->data)) + return (1); + } else { + msg_warn("%s: skipping record type %s for query %s", + myname, dns_strtype(rr->type), rr->qname); + } + } return (0); } @@ -3167,6 +3196,7 @@ static const SMTPD_RBL_STATE *find_dnsxl_addr(SMTPD_STATE *state, int i; SMTPD_RBL_STATE *rbl; const char *reply_addr; + const char *byte_codes; struct addrinfo *res; unsigned char *ipv6_addr; @@ -3210,12 +3240,14 @@ static const SMTPD_RBL_STATE *find_dnsxl_addr(SMTPD_STATE *state, vstring_strcat(query, rbl_domain); reply_addr = split_at(STR(query), '='); rbl = (SMTPD_RBL_STATE *) ctable_locate(smtpd_rbl_cache, STR(query)); + if (reply_addr != 0) + byte_codes = ctable_locate(smtpd_rbl_byte_cache, reply_addr); /* * If the record exists, match the result address. */ if (SMTPD_DNSXL_STAT_OK(rbl) && reply_addr != 0 - && !rbl_match_addr(rbl, reply_addr)) + && !rbl_match_addr(rbl, byte_codes)) rbl = 0; vstring_free(query); return (rbl); @@ -3284,6 +3316,7 @@ static const SMTPD_RBL_STATE *find_dnsxl_domain(SMTPD_STATE *state, SMTPD_RBL_STATE *rbl; const char *domain; const char *reply_addr; + const char *byte_codes; /* * Extract the domain, tack on the RBL domain name and query the DNS for @@ -3302,12 +3335,14 @@ static const SMTPD_RBL_STATE *find_dnsxl_domain(SMTPD_STATE *state, vstring_sprintf(query, "%s.%s", domain, rbl_domain); reply_addr = split_at(STR(query), '='); rbl = (SMTPD_RBL_STATE *) ctable_locate(smtpd_rbl_cache, STR(query)); + if (reply_addr != 0) + byte_codes = ctable_locate(smtpd_rbl_byte_cache, reply_addr); /* * If the record exists, match the result address. */ if (SMTPD_DNSXL_STAT_OK(rbl) && reply_addr != 0 - && !rbl_match_addr(rbl, reply_addr)) + && !rbl_match_addr(rbl, byte_codes)) rbl = 0; vstring_free(query); return (rbl); diff --git a/postfix/src/smtpd/smtpd_peer.c b/postfix/src/smtpd/smtpd_peer.c index 25ca3d306..2aa921774 100644 --- a/postfix/src/smtpd/smtpd_peer.c +++ b/postfix/src/smtpd/smtpd_peer.c @@ -113,6 +113,7 @@ #include #include #include +#include /* Global library. */ @@ -224,6 +225,14 @@ void smtpd_peer_init(SMTPD_STATE *state) myname, MAI_STRERROR(aierr)); state->port = mystrdup(client_port.buf); + /* + * XXX Strip off the IPv6 datalink suffix to avoid false alarms with + * strict address syntax checks. + */ +#ifdef HAS_IPV6 + (void) split_at(client_addr.buf, '%'); +#endif + /* * We convert IPv4-in-IPv6 address to 'true' IPv4 address early on, * but only if IPv4 support is enabled (why would anyone want to turn diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in index e79a4accd..b07f74fdb 100644 --- a/postfix/src/util/Makefile.in +++ b/postfix/src/util/Makefile.in @@ -32,7 +32,8 @@ SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \ write_buf.c write_wait.c sane_basename.c format_tv.c allspace.c \ allascii.c load_file.c killme_after.c vstream_tweak.c upass_connect.c \ upass_listen.c upass_trigger.c edit_file.c inet_windowsize.c \ - unix_pass_fd_fix.c dict_cache.c valid_utf_8.c dict_thash.c + unix_pass_fd_fix.c dict_cache.c valid_utf_8.c dict_thash.c \ + ip_match.c ip_lmatch.c OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \ attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \ attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \ @@ -66,7 +67,8 @@ OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \ write_buf.o write_wait.o sane_basename.o format_tv.o allspace.o \ allascii.o load_file.o killme_after.o vstream_tweak.o upass_connect.o \ upass_listen.o upass_trigger.o edit_file.o inet_windowsize.o \ - unix_pass_fd_fix.o dict_cache.o valid_utf_8.o dict_thash.o + unix_pass_fd_fix.o dict_cache.o valid_utf_8.o dict_thash.o \ + ip_match.o ip_lmatch.o HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \ chroot_uid.h cidr_match.h clean_env.h connect.h ctable.h dict.h \ dict_cdb.h dict_cidr.h dict_db.h dict_dbm.h dict_env.h dict_ht.h \ @@ -86,7 +88,8 @@ HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \ stringops.h sys_defs.h timed_connect.h timed_wait.h trigger.h \ username.h valid_hostname.h vbuf.h vbuf_print.h vstream.h vstring.h \ vstring_vstream.h watchdog.h format_tv.h load_file.h killme_after.h \ - edit_file.h dict_cache.h dict_thash.h + edit_file.h dict_cache.h dict_thash.h \ + ip_match.h ip_lmatch.h TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \ stream_test.c dup2_pass_on_exec.c test_send_fd test_recv_fd DEFS = -I. -D$(SYSTYPE) @@ -104,7 +107,7 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \ attr_scan0 host_port attr_scan_plain attr_print_plain htable \ unix_recv_fd unix_send_fd stream_recv_fd stream_send_fd hex_code \ myaddrinfo myaddrinfo4 inet_proto sane_basename format_tv \ - test_send_fd test_recv_fd valid_utf_8 + test_send_fd test_recv_fd valid_utf_8 ip_match ip_lmatch LIB_DIR = ../../lib INC_DIR = ../../include @@ -147,7 +150,7 @@ shar: @shar $(FILES) lint: - lint $(SRCS) + lint $(DEFS) $(SRCS) $(LINTFIX) clean: rm -f *.o $(LIB) *core $(TESTPROG) junk $(MAKES) *.tmp @@ -419,11 +422,21 @@ valid_utf_8: $(LIB) $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) mv junk $@.o +ip_match: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +ip_lmatch: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + tests: valid_hostname_test mac_expand_test dict_test unescape_test \ hex_quote_test ctable_test inet_addr_list_test base64_code_test \ attr_scan64_test attr_scan0_test dict_pcre_test host_port_test \ dict_cidr_test attr_scan_plain_test htable_test hex_code_test \ - myaddrinfo_test format_tv_test + myaddrinfo_test format_tv_test ip_match_test ip_lmatch_test root_tests: @@ -545,6 +558,16 @@ format_tv_test: format_tv format_tv.in format_tv.ref diff format_tv.ref format_tv.tmp rm -f format_tv.tmp +ip_match_test: ip_match ip_match.in ip_match.ref + ./ip_match ip_match.tmp + diff ip_match.ref ip_match.tmp + rm -f ip_match.tmp + +ip_lmatch_test: ip_lmatch ip_lmatch.in ip_lmatch.ref + ./ip_lmatch ip_lmatch.tmp + diff ip_lmatch.ref ip_lmatch.tmp + rm -f ip_lmatch.tmp + depend: $(MAKES) (sed '1,/^# do not edit/!d' Makefile.in; \ set -e; for i in [a-z][a-z0-9]*.c; do \ @@ -1184,6 +1207,20 @@ inet_windowsize.o: inet_windowsize.c inet_windowsize.o: iostuff.h inet_windowsize.o: msg.h inet_windowsize.o: sys_defs.h +ip_match.o: ip_match.c +ip_match.o: ip_match.h +ip_match.o: msg.h +ip_match.o: mymalloc.h +ip_match.o: sys_defs.h +ip_match.o: vbuf.h +ip_match.o: vstring.h +ip_lmatch.o: ip_lmatch.c +ip_lmatch.o: ip_lmatch.h +ip_lmatch.o: msg.h +ip_lmatch.o: mymalloc.h +ip_lmatch.o: sys_defs.h +ip_lmatch.o: vbuf.h +ip_lmatch.o: vstring.h killme_after.o: killme_after.c killme_after.o: killme_after.h killme_after.o: sys_defs.h diff --git a/postfix/src/util/ip_lmatch.c b/postfix/src/util/ip_lmatch.c new file mode 100644 index 000000000..70dc1ecbb --- /dev/null +++ b/postfix/src/util/ip_lmatch.c @@ -0,0 +1,450 @@ +/*++ +/* NAME +/* ip_lmatch 3 +/* SUMMARY +/* lazy IP address pattern matching +/* SYNOPSIS +/* #include +/* +/* int ip_lmatch(pattern, addr, why) +/* char *pattern; +/* const char *addr; +/* VSTRING **why; +/* DESCRIPTION +/* This module supports IP address pattern matching. See below +/* for a description of the supported address pattern syntax. +/* +/* This version optimizes for implementation convenience. The +/* lazy parser stops as soon as the address does not match the +/* pattern. This results in a poor user interface: a pattern +/* syntax error at the end will be reported ONLY when an address +/* matches the entire pattern before the syntax error. +/* +/* Use the ip_match() module for an implementation that has +/* separate parsing and matching stages. That implementation +/* reports a syntax error immediately, and provides faster +/* matching at the cost of a more complex programming interface. +/* +/* ip_lmatch_parse() matches the address bytes while parsing +/* the pattern, and terminates as soon as a non-match or syntax +/* error is found. The result is -1 in case of syntax error, +/* 0 in case of no match, 1 in case of a match. +/* +/* Arguments +/* .IP addr +/* Network address in printable form. +/* .IP pattern +/* Address pattern. This argument may be modified. +/* .IP why +/* Pointer to storage for error reports (result value -1). If +/* the target is a null pointer, ip_lmatch() will allocate a +/* buffer that should be freed by the application. +/* IPV4 PATTERN SYNTAX +/* .ad +/* .fi +/* An IPv4 address pattern has four fields separated by ".". +/* Each field is either a decimal number, or a sequence inside +/* "[]" that contains one or more comma-separated decimal +/* numbers or number..number ranges. +/* +/* Examples of patterns are 1.2.3.4 (matches itself, as one +/* would expect) and 1.2.3.[2,4,6..8] (matches 1.2.3.2, 1.2.3.4, +/* 1.2.3.6, 1.2.3.7, 1.2.3.8). +/* +/* Thus, any pattern field can be a sequence inside "[]", but +/* a "[]" sequence cannot span multiple address fields, and +/* a pattern field cannot contain both a number and a "[]" +/* sequence at the same time. +/* +/* This means that the pattern 1.2.[3.4] is not valid (the +/* sequence [3.4] cannot span two address fields) and the +/* pattern 1.2.3.3[6..9] is also not valid (the last field +/* cannot be both number 3 and sequence [6..9] at the same +/* time). +/* +/* The syntax for IPv4 patterns is as follows: +/* +/* .in +5 +/* v4pattern = v4field "." v4field "." v4field "." v4field +/* .br +/* v4field = v4octet | "[" v4sequence "] +/* .br +/* v4octet = any decimal number in the range 0 through 255 +/* .br +/* v4sequence = v4seq_member | v4sequence "," v4seq_member +/* .br +/* v4seq_member = v4octet | v4octet ".." v4octet +/* .in +/* 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 +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + + /* + * Token values. + */ +#define IP_LMATCH_CODE_OPEN '[' /* start of set */ +#define IP_LMATCH_CODE_CLOSE ']' /* end of set */ +#define IP_LMATCH_CODE_OVAL 'N' /* octet value */ +#define IP_LMATCH_CODE_EOF '\0' /* oops */ +#define IP_LMATCH_CODE_ERR 256 /* oops */ + + /* + * Address length is protocol dependent. Find out how large our address byte + * strings should be. + */ +#ifdef HAS_IPV6 +#define IP_LMATCH_ABYTES MAI_V6ADDR_BYTES +#else +#define IP_LMATCH_ABYTES MAI_V4ADDR_BYTES +#endif + + /* + * SLMs. + */ +#define STR vstring_str +#define LEN VSTRING_LEN + +/* ip_lmatch_next_token - carve out the next token from user input */ + +static int ip_lmatch_next_token(char **pstart, char **psaved_start, int *poval) +{ + unsigned char *cp; + unsigned char *next; + int oval; + + /* + * Return a value-less token (i.e. a literal, error, or EOF. + */ +#define IP_LMATCH_RETURN_TOK(next, type) \ + do { *pstart = (char *) (next); return (type); } while (0) + + /* + * Return a token that contains an IPv4 address octet value. + */ +#define IP_LMATCH_RETURN_TOK_OVAL(next, oval) do { \ + *poval = (oval); IP_LMATCH_RETURN_TOK((next), IP_LMATCH_CODE_OVAL); \ + } while (0) + + /* + * Light-weight tokenizer. Each result is an IPv4 address octet value, a + * literal character value, error, or EOF. + */ + *psaved_start = *pstart; + cp = (unsigned char *) *pstart; + if (ISDIGIT(*cp)) { + oval = *cp - '0'; + for (next = cp + 1; ISDIGIT(*next); next++) { + oval *= 10; + oval += *next - '0'; + if (oval > 255) + IP_LMATCH_RETURN_TOK(next + 1, IP_LMATCH_CODE_ERR); + } + IP_LMATCH_RETURN_TOK_OVAL(next, oval); + } else { + IP_LMATCH_RETURN_TOK(*cp ? cp + 1 : cp, *cp); + } +} + +/* ip_lmatch_print_parse_error - report parsing error in context */ + +static void PRINTFLIKE(5, 6) ip_lmatch_print_parse_error(VSTRING **why, + char *start, + char *here, + char *next, + const char *fmt,...) +{ + va_list ap; + int start_width; + int here_width; + + /* + * On-the-fly allocation. + */ + if (*why == 0) + *why = vstring_alloc(20); + + /* + * Format the error type. + */ + va_start(ap, fmt); + vstring_vsprintf(*why, fmt, ap); + va_end(ap); + + /* + * Format the error context. The syntax is complex enough that it is + * worth the effort to precisely indicate what input is in error. + * + * XXX Workaround for %.*s to avoid output when a zero width is specified. + */ +#define IP_LMATCH_NO_ERROR_CONTEXT (char *) 0, (char *) 0, (char *) 0 + + if (start != 0) { + start_width = here - start; + here_width = next - here; + vstring_sprintf_append(*why, " at \"%.*s>%.*s<%s\"", + start_width, start_width == 0 ? "" : start, + here_width, here_width == 0 ? "" : here, next); + } +} + +/* ip_lmatch - match an address pattern */ + +int ip_lmatch(char *pattern, const char *addr, VSTRING **why) +{ + const char *myname = "ip_lmatch"; + char addr_bytes[IP_LMATCH_ABYTES]; + const unsigned char *ap; + int octet_count; + char *saved_cp; + char *cp; + int token_type; + int look_ahead; + int oval; + int saved_oval; + int matched; + + /* + * For now, IPv4 support only. Use different parser loops for IPv4 and + * IPv6. + */ + switch (inet_pton(AF_INET, addr, addr_bytes)) { + case -1: + msg_fatal("%s: address conversion error: %m", myname); + case 0: + msg_warn("%s: unexpected address form: %s", myname, addr); + return (0); + } + + /* + * Simplify this if we change to {} for "octet set" notation. + */ +#define FIND_TERMINATOR(start, cp) do { \ + int _level = 1; \ + for (cp = (start) ; *cp; cp++) { \ + if (*cp == '[') _level++; \ + if (*cp != ']') continue; \ + if (--_level == 0) break; \ + } \ + } while (0) + + /* + * Strip [] around the entire pattern. + */ + if (*pattern == '[') { + FIND_TERMINATOR(pattern, cp); + if (cp[0] == 0) { + ip_lmatch_print_parse_error(why, IP_LMATCH_NO_ERROR_CONTEXT, + "missing \"]\" character"); + return (-1); + } + if (cp[1] == 0) { + *cp = 0; + pattern += 1; + } + } + + /* + * Sanity check. In this case we can't show any error context. + */ + if (*pattern == 0) { + ip_lmatch_print_parse_error(why, IP_LMATCH_NO_ERROR_CONTEXT, + "empty address pattern"); + return (-1); + } + + /* + * Simple on-the-fly pattern matching. + */ + octet_count = 0; + cp = pattern; + + /* + * Require four address fields separated by ".", each field containing a + * numeric octet value or a sequence inside []. The loop head has no test + * and does not step the loop variable. The tokenizer advances the loop + * variable, and the loop termination logic is inside the loop. + */ + for (ap = (const unsigned char *) addr_bytes; /* void */ ; ap++) { + switch (token_type = ip_lmatch_next_token(&cp, &saved_cp, &oval)) { + + /* + * Numeric address field. + */ + case IP_LMATCH_CODE_OVAL: + if (*ap == oval) + break; + return (0); + + /* + * Wild-card address field. + */ + case IP_LMATCH_CODE_OPEN: + matched = 0; + /* Require comma-separated numbers or numeric ranges. */ + for (;;) { + token_type = ip_lmatch_next_token(&cp, &saved_cp, &oval); + if (token_type == IP_LMATCH_CODE_OVAL) { + saved_oval = oval; + look_ahead = ip_lmatch_next_token(&cp, &saved_cp, &oval); + /* Numeric range. */ + if (look_ahead == '.') { + /* Brute-force parsing. */ + if (ip_lmatch_next_token(&cp, &saved_cp, &oval) == '.' + && ip_lmatch_next_token(&cp, &saved_cp, &oval) + == IP_LMATCH_CODE_OVAL + && saved_oval <= oval) { + if (!matched) + matched = (*ap >= saved_oval && *ap <= oval); + look_ahead = + ip_lmatch_next_token(&cp, &saved_cp, &oval); + } else { + ip_lmatch_print_parse_error(why, pattern, + saved_cp, cp, + "numeric range error"); + return (-1); + } + } + /* Single number. */ + else { + if (!matched) + matched = (*ap == oval); + } + /* Require "," or end-of-wildcard. */ + token_type = look_ahead; + if (token_type == ',') { + continue; + } else if (token_type == IP_LMATCH_CODE_CLOSE) { + break; + } else { + ip_lmatch_print_parse_error(why, pattern, saved_cp, cp, + "need \",\" or \"%c\"", + IP_LMATCH_CODE_CLOSE); + return (-1); + } + } else { + ip_lmatch_print_parse_error(why, pattern, saved_cp, cp, + "need decimal number 0..255"); + return (-1); + } + } + if (matched == 0) + return (0); + break; + + /* + * Invalid field. + */ + default: + ip_lmatch_print_parse_error(why, pattern, saved_cp, cp, + "need decimal number 0..255 or \"%c\"", + IP_LMATCH_CODE_OPEN); + return (-1); + } + octet_count += 1; + + /* + * Require four address fields. Not one more, not one less. + */ + if (octet_count == 4) { + if (*cp != 0) { + (void) ip_lmatch_next_token(&cp, &saved_cp, &oval); + ip_lmatch_print_parse_error(why, pattern, saved_cp, cp, + "garbage after pattern"); + return (-1); + } + return (1); + } + + /* + * Require "." before the next address field. + */ + if (ip_lmatch_next_token(&cp, &saved_cp, &oval) != '.') { + ip_lmatch_print_parse_error(why, pattern, saved_cp, cp, + "need \".\""); + return (-1); + } + } +} + +#ifdef TEST + + /* + * Dummy main program for regression tests. + */ +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + VSTRING *why = vstring_alloc(100); + VSTRING *line_buf = vstring_alloc(100); + char *bufp; + char *user_pattern; + char *user_address; + int echo_input = !isatty(0); + int match_status; + + /* + * Iterate over the input stream. The input format is a pattern, followed + * by addresses to match against. + */ + while (vstring_fgets_nonl(line_buf, VSTREAM_IN)) { + bufp = STR(line_buf); + if (echo_input) { + vstream_printf("> %s\n", bufp); + vstream_fflush(VSTREAM_OUT); + } + if (*bufp == '#') + continue; + if ((user_pattern = mystrtok(&bufp, " \t")) == 0) + continue; + + /* + * Match the patterns. + */ + while ((user_address = mystrtok(&bufp, " \t")) != 0) { + match_status = ip_lmatch(user_pattern, user_address, &why); + if (match_status < 0) { + vstream_printf("Error: %s\n", STR(why)); + } else { + vstream_printf("Match %s: %s\n", user_address, + match_status ? "yes" : "no"); + } + vstream_fflush(VSTREAM_OUT); + } + } + vstring_free(line_buf); + vstring_free(why); + exit(0); +} + +#endif diff --git a/postfix/src/util/ip_lmatch.h b/postfix/src/util/ip_lmatch.h new file mode 100644 index 000000000..788956f88 --- /dev/null +++ b/postfix/src/util/ip_lmatch.h @@ -0,0 +1,35 @@ +#ifndef _IP_LMATCH_H_INCLUDED_ +#define _IP_LMATCH_H_INCLUDED_ + +/*++ +/* NAME +/* ip_lmatch 3h +/* SUMMARY +/* lazy IP address pattern matching +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern int ip_lmatch(char *, const char *, VSTRING **); + +/* 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 +/*--*/ + +#endif diff --git a/postfix/src/util/ip_lmatch.in b/postfix/src/util/ip_lmatch.in new file mode 100644 index 000000000..9b7aba33a --- /dev/null +++ b/postfix/src/util/ip_lmatch.in @@ -0,0 +1,21 @@ +1.2.3.4 1.2.3.4 +1.2.300.4 1.2.3.4 +1.2.3. 1.2.3.4 +1.2.3 1.2.3.4 +a 1.2.3.4 +1.2.3,4 1.2.3.4 +1.2.[3].4 1.2.3.4 +1.2.[].4 1.2.3.4 +1.2.[.4 1.2.3.4 +1.2.].4 1.2.3.4 +1.2.[1..127,128..255].5 1.2.3.4 +1.2.[1-255].5 1.2.3.4 +1.2.[1..127.128..255].5 1.2.3.4 +1.2.3.[4] 1.2.3.4 +1.2.3.[4..1] 1.2.3.4 +1.2.3.[4.1] 1.2.3.4 +1.2.3.[4.x] 1.2.3.4 +1.2.3.[x] 1.2.3.4 +1.2.3.4x 1.2.3.4 +1.2.[3..11].5 1.2.3.5 1.2.2.5 1.2.11.5 1.2.12.5 1.2.11.6 +1.2.[3,5,7,9,11].5 1.2.3.5 1.2.2.5 1.2.4.5 1.2.11.5 1.2.12.5 1.2.11.6 diff --git a/postfix/src/util/ip_lmatch.ref b/postfix/src/util/ip_lmatch.ref new file mode 100644 index 000000000..d8431bd91 --- /dev/null +++ b/postfix/src/util/ip_lmatch.ref @@ -0,0 +1,51 @@ +> 1.2.3.4 1.2.3.4 +Match 1.2.3.4: yes +> 1.2.300.4 1.2.3.4 +Error: need decimal number 0..255 or "[" at "1.2.>300<.4" +> 1.2.3. 1.2.3.4 +Error: need decimal number 0..255 or "[" at "1.2.3.><" +> 1.2.3 1.2.3.4 +Error: need "." at "1.2.3><" +> a 1.2.3.4 +Error: need decimal number 0..255 or "[" at ">a<" +> 1.2.3,4 1.2.3.4 +Error: need "." at "1.2.3>,<4" +> 1.2.[3].4 1.2.3.4 +Match 1.2.3.4: yes +> 1.2.[].4 1.2.3.4 +Error: need decimal number 0..255 at "1.2.[>]<.4" +> 1.2.[.4 1.2.3.4 +Error: need decimal number 0..255 at "1.2.[>.<4" +> 1.2.].4 1.2.3.4 +Error: need decimal number 0..255 or "[" at "1.2.>]<.4" +> 1.2.[1..127,128..255].5 1.2.3.4 +Match 1.2.3.4: no +> 1.2.[1-255].5 1.2.3.4 +Error: need "," or "]" at "1.2.[1>-<255].5" +> 1.2.[1..127.128..255].5 1.2.3.4 +Error: need "," or "]" at "1.2.[1..127>.<128..255].5" +> 1.2.3.[4] 1.2.3.4 +Match 1.2.3.4: yes +> 1.2.3.[4..1] 1.2.3.4 +Error: numeric range error at "1.2.3.[4..>1<]" +> 1.2.3.[4.1] 1.2.3.4 +Error: numeric range error at "1.2.3.[4.>1<]" +> 1.2.3.[4.x] 1.2.3.4 +Error: numeric range error at "1.2.3.[4.>x<]" +> 1.2.3.[x] 1.2.3.4 +Error: need decimal number 0..255 at "1.2.3.[>x<]" +> 1.2.3.4x 1.2.3.4 +Error: garbage after pattern at "1.2.3.4>x<" +> 1.2.[3..11].5 1.2.3.5 1.2.2.5 1.2.11.5 1.2.12.5 1.2.11.6 +Match 1.2.3.5: yes +Match 1.2.2.5: no +Match 1.2.11.5: yes +Match 1.2.12.5: no +Match 1.2.11.6: no +> 1.2.[3,5,7,9,11].5 1.2.3.5 1.2.2.5 1.2.4.5 1.2.11.5 1.2.12.5 1.2.11.6 +Match 1.2.3.5: yes +Match 1.2.2.5: no +Match 1.2.4.5: no +Match 1.2.11.5: yes +Match 1.2.12.5: no +Match 1.2.11.6: no diff --git a/postfix/src/util/ip_match.c b/postfix/src/util/ip_match.c new file mode 100644 index 000000000..17d1db29c --- /dev/null +++ b/postfix/src/util/ip_match.c @@ -0,0 +1,675 @@ +/*++ +/* NAME +/* ip_match 3 +/* SUMMARY +/* IP address pattern matching +/* SYNOPSIS +/* #include +/* +/* char *ip_match_parse(byte_codes, pattern) +/* VSTRING *byte_codes; +/* char *pattern; +/* +/* char *ip_match_save(byte_codes) +/* const VSTRING *byte_codes; +/* +/* int ip_match_execute(byte_codes, addr_bytes) +/* cost char *byte_codes; +/* const char *addr_bytes; +/* +/* char *ip_match_dump(printable, byte_codes) +/* VSTRING *printable; +/* const char *byte_codes; +/* DESCRIPTION +/* This module supports IP address pattern matching. See below +/* for a description of the supported address pattern syntax. +/* +/* This implementation aims to minimize the cost of translating +/* the pattern to internal form, while still providing good +/* matching performance in the typical case. The first byte +/* of an encoded pattern specifies the expected address family +/* (for example, AF_INET); other details of the encoding are +/* private and are subject to change. +/* +/* ip_match_parse() converts the user-specified pattern to +/* internal form. The result value is a null pointer in case +/* of success, or a pointer into the byte_codes buffer with a +/* detailed problem description. +/* +/* ip_match_save() saves the result from ip_match_parse() for +/* longer-term usage. The result should be passed to myfree(). +/* +/* ip_match_execute() matches a binary network in addr_bytes +/* against a byte-code array in byte_codes. It is an error to +/* use different address families for the byte_codes and addr_bytes +/* arguments (the first byte-code value contains the expected +/* address family). The result is non-zero in case of success. +/* +/* ip_match_dump() produces an ASCII dump of a byte-code array. +/* The dump is supposed to be identical to the input pattern +/* modulo upper/lower case or leading nulls with IPv6). This +/* function is primarily a debugging aid. +/* +/* Arguments +/* .IP addr_bytes +/* Binary network address in network-byte order. +/* .IP byte_codes +/* Byte-code array produced by ip_match_parse(). +/* .IP pattern +/* Human-readable address pattern. +/* .IP printable +/* storage for ASCII dump of a byte-code array. +/* IPV4 PATTERN SYNTAX +/* .ad +/* .fi +/* An IPv4 address pattern has four fields separated by ".". +/* Each field is either a decimal number, or a sequence inside +/* "[]" that contains one or more comma-separated decimal +/* numbers or number..number ranges. +/* +/* Examples of patterns are 1.2.3.4 (matches itself, as one +/* would expect) and 1.2.3.[2,4,6..8] (matches 1.2.3.2, 1.2.3.4, +/* 1.2.3.6, 1.2.3.7, 1.2.3.8). +/* +/* Thus, any pattern field can be a sequence inside "[]", but +/* a "[]" sequence cannot span multiple address fields, and +/* a pattern field cannot contain both a number and a "[]" +/* sequence at the same time. +/* +/* This means that the pattern 1.2.[3.4] is not valid (the +/* sequence [3.4] cannot span two address fields) and the +/* pattern 1.2.3.3[6..9] is also not valid (the last field +/* cannot be both number 3 and sequence [6..9] at the same +/* time). +/* +/* The syntax for IPv4 patterns is as follows: +/* +/* .in +5 +/* v4pattern = v4field "." v4field "." v4field "." v4field +/* .br +/* v4field = v4octet | "[" v4sequence "]" +/* .br +/* v4octet = any decimal number in the range 0 through 255 +/* .br +/* v4sequence = v4seq_member | v4sequence "," v4seq_member +/* .br +/* v4seq_member = v4octet | v4octet ".." v4octet +/* .in +/* 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 +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + + /* + * Token values. The in-band values are also used as byte-code values. + */ +#define IP_MATCH_CODE_OPEN '[' /* in-band */ +#define IP_MATCH_CODE_CLOSE ']' /* in-band */ +#define IP_MATCH_CODE_OVAL 'N' /* in-band */ +#define IP_MATCH_CODE_RANGE 'R' /* in-band */ +#define IP_MATCH_CODE_EOF '\0' /* in-band */ +#define IP_MATCH_CODE_ERR 256 /* out-of-band */ + + /* + * SLMs. + */ +#define STR vstring_str +#define LEN VSTRING_LEN + +/* ip_match_save - make longer-term copy of byte code */ + +char *ip_match_save(const VSTRING *byte_codes) +{ + char *dst; + + dst = mymalloc(LEN(byte_codes)); + return (memcpy(dst, STR(byte_codes), LEN(byte_codes))); +} + +/* ip_match_dump - byte-code pretty printer */ + +char *ip_match_dump(VSTRING *printable, const char *byte_codes) +{ + const char *myname = "ip_match_dump"; + const unsigned char *bp; + int octet_count = 0; + int ch; + + /* + * Sanity check. Use different dumping loops for AF_INET and AF_INET6. + */ + if (*byte_codes != AF_INET) + msg_panic("%s: malformed byte-code header", myname); + + /* + * Pretty-print and sanity-check the byte codes. Note: the loops in this + * code have no auto-increment at the end of the iteration. Instead, each + * byte-code handler bumps the byte-code pointer appropriately. + */ + VSTRING_RESET(printable); + bp = (const unsigned char *) byte_codes + 1; + for (;;) { + + /* + * Simple numeric field. + */ + if ((ch = *bp++) == IP_MATCH_CODE_OVAL) { + vstring_sprintf_append(printable, "%d", *bp); + bp += 1; + } + + /* + * Wild-card numeric field. + */ + else if (ch == IP_MATCH_CODE_OPEN) { + vstring_sprintf_append(printable, "["); + for (;;) { + /* Numeric range. */ + if ((ch = *bp++) == IP_MATCH_CODE_RANGE) { + vstring_sprintf_append(printable, "%d..%d", bp[0], bp[1]); + bp += 2; + } + /* Number. */ + else if (ch == IP_MATCH_CODE_OVAL) { + vstring_sprintf_append(printable, "%d", *bp); + bp += 1; + } + /* End-of-wildcard. */ + else if (ch == IP_MATCH_CODE_CLOSE) { + break; + } + /* Corruption. */ + else { + msg_panic("%s: unexpected byte code (decimal %d) " + "after \"%s\"", myname, ch, STR(printable)); + } + /* Output the wild-card field separator and repeat the loop. */ + if (*bp != IP_MATCH_CODE_CLOSE) + vstring_sprintf_append(printable, ","); + } + vstring_sprintf_append(printable, "]"); + } + + /* + * Corruption. + */ + else { + msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"", + myname, ch, STR(printable)); + } + + /* + * Require four octets, not one more, not one less. + */ + if (++octet_count == 4) { + if (*bp != 0) + msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"", + myname, ch, STR(printable)); + return (STR(printable)); + } + if (*bp == 0) + msg_panic("%s: truncated byte code after \"%s\"", + myname, STR(printable)); + + /* + * Output the address field separator and repeat the loop. + */ + vstring_sprintf_append(printable, "."); + } +} + +/* ip_match_print_code_prefix - printable byte-code prefix */ + +static char *ip_match_print_code_prefix(const char *byte_codes, size_t len) +{ + static VSTRING *printable = 0; + const char *fmt; + const char *bp; + + /* + * This is primarily for emergency debugging so we don't care about + * non-reentrancy. + */ + if (printable == 0) + printable = vstring_alloc(100); + else + VSTRING_RESET(printable); + + /* + * Use decimal for IPv4 and hexadecimal otherwise, so that address octet + * values are easy to recognize. + */ + fmt = (*byte_codes == AF_INET ? "%d " : "%02x "); + for (bp = byte_codes; bp < byte_codes + len; bp++) + vstring_sprintf_append(printable, fmt, *(const unsigned char *) bp); + + return (STR(printable)); +} + +/* ip_match_execute - byte-code matching engine */ + +int ip_match_execute(const char *byte_codes, const char *addr_bytes) +{ + const char *myname = "ip_match_execute"; + const unsigned char *bp; + const unsigned char *ap; + int octet_count = 0; + int ch; + int matched; + + /* + * Sanity check. Use different execute loops for AF_INET and AF_INET6. + */ + if (*byte_codes != AF_INET) + msg_panic("%s: malformed byte-code header (decimal %d)", + myname, *(const unsigned char *) byte_codes); + + /* + * Match the address bytes against the byte codes. Avoid problems with + * (char -> int) sign extension on architectures with signed characters. + */ + bp = (const unsigned char *) byte_codes + 1; + ap = (const unsigned char *) addr_bytes; + + for (octet_count = 0; octet_count < 4; octet_count++, ap++) { + + /* + * Simple numeric field. + */ + if ((ch = *bp++) == IP_MATCH_CODE_OVAL) { + if (*ap == *bp) + bp += 1; + else + return (0); + } + + /* + * Wild-card numeric field. + */ + else if (ch == IP_MATCH_CODE_OPEN) { + matched = 0; + for (;;) { + /* Numeric range. */ + if ((ch = *bp++) == IP_MATCH_CODE_RANGE) { + if (!matched) + matched = (*ap >= bp[0] && *ap <= bp[1]); + bp += 2; + } + /* Number. */ + else if (ch == IP_MATCH_CODE_OVAL) { + if (!matched) + matched = (*ap == *bp); + bp += 1; + } + /* End-of-wildcard. */ + else if (ch == IP_MATCH_CODE_CLOSE) { + break; + } + /* Corruption. */ + else { + size_t len = (const char *) bp - byte_codes - 1; + + msg_panic("%s: unexpected byte code (decimal %d) " + "after \"%s\"", myname, ch, + ip_match_print_code_prefix(byte_codes, len)); + } + } + if (matched == 0) + return (0); + } + + /* + * Corruption. + */ + else { + size_t len = (const char *) bp - byte_codes - 1; + + msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"", + myname, ch, ip_match_print_code_prefix(byte_codes, len)); + } + } + return (1); +} + +/* ip_match_next_token - carve out the next token from input pattern */ + +static int ip_match_next_token(char **pstart, char **psaved_start, int *poval) +{ + unsigned char *cp; + unsigned char *next; + int oval; + + /* + * Return a literal, error, or EOF token. Update the read pointer to the + * start of the next token or leave it at the string terminator. + */ +#define IP_MATCH_RETURN_TOK(next, type) \ + do { *pstart = (char *) (next); return (type); } while (0) + + /* + * Return a token that contains an IPv4 address octet value. + */ +#define IP_MATCH_RETURN_TOK_OVAL(next, oval) do { \ + *poval = (oval); IP_MATCH_RETURN_TOK((next), IP_MATCH_CODE_OVAL); \ + } while (0) + + /* + * Light-weight tokenizer. Each result is an IPv4 address octet value, a + * literal character value, error, or EOF. + */ + *psaved_start = *pstart; + cp = (unsigned char *) *pstart; + if (ISDIGIT(*cp)) { + oval = *cp - '0'; + for (next = cp + 1; ISDIGIT(*next); next++) { + oval *= 10; + oval += *next - '0'; + if (oval > 255) + IP_MATCH_RETURN_TOK(next + 1, IP_MATCH_CODE_ERR); + } + IP_MATCH_RETURN_TOK_OVAL(next, oval); + } else { + IP_MATCH_RETURN_TOK(*cp ? cp + 1 : cp, *cp); + } +} + +/* ipmatch_print_parse_error - formatted parsing error, with context */ + +static void PRINTFLIKE(5, 6) ipmatch_print_parse_error(VSTRING *reply, + char *start, + char *here, + char *next, + const char *fmt,...) +{ + va_list ap; + int start_width; + int here_width; + + /* + * Format the error type. + */ + va_start(ap, fmt); + vstring_vsprintf(reply, fmt, ap); + va_end(ap); + + /* + * Format the error context. The syntax is complex enough that it is + * worth the effort to precisely indicate what input is in error. + * + * XXX Workaround for %.*s to avoid output when a zero width is specified. + */ + if (start != 0) { + start_width = here - start; + here_width = next - here; + vstring_sprintf_append(reply, " at \"%.*s>%.*s<%s\"", + start_width, start_width == 0 ? "" : start, + here_width, here_width == 0 ? "" : here, next); + } +} + +/* ip_match_parse - parse an entire wild-card address pattern */ + +char *ip_match_parse(VSTRING *byte_codes, char *pattern) +{ + int octet_count; + char *saved_cp; + char *cp; + int token_type; + int look_ahead; + int oval; + int saved_oval; + + /* + * Simplify this if we change to {} for wildcard notation. + */ +#define FIND_TERMINATOR(start, cp) do { \ + int _level = 1; \ + for (cp = (start) ; *cp; cp++) { \ + if (*cp == '[') _level++; \ + if (*cp != ']') continue; \ + if (--_level == 0) break; \ + } \ + } while (0) + + /* + * Strip [] around the entire pattern. + */ + if (*pattern == '[') { + FIND_TERMINATOR(pattern, cp); + if (cp[0] == 0) { + vstring_sprintf(byte_codes, "missing \"]\" character"); + return (STR(byte_codes)); + } + if (cp[1] == 0) { + *cp = 0; + pattern += 1; + } + } + + /* + * Sanity check. In this case we can't show any error context. + */ + if (*pattern == 0) { + vstring_sprintf(byte_codes, "empty address pattern"); + return (STR(byte_codes)); + } + + /* + * Simple parser with on-the-fly encoding. For now, IPv4 support only. + * Use different parser loops for IPv4 and IPv6. + */ + VSTRING_RESET(byte_codes); + VSTRING_ADDCH(byte_codes, AF_INET); + octet_count = 0; + cp = pattern; + + /* + * Require four address fields separated by ".", each field containing a + * numeric octet value or a sequence inside []. The loop head has no test + * and does not step the loop variable. The tokenizer advances the loop + * variable, and the loop termination logic is inside the loop. + */ + for (;;) { + switch (token_type = ip_match_next_token(&cp, &saved_cp, &oval)) { + + /* + * Numeric address field. + */ + case IP_MATCH_CODE_OVAL: + VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OVAL); + VSTRING_ADDCH(byte_codes, oval); + break; + + /* + * Wild-card address field. + */ + case IP_MATCH_CODE_OPEN: + VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OPEN); + /* Require comma-separated numbers or numeric ranges. */ + for (;;) { + token_type = ip_match_next_token(&cp, &saved_cp, &oval); + if (token_type == IP_MATCH_CODE_OVAL) { + saved_oval = oval; + look_ahead = ip_match_next_token(&cp, &saved_cp, &oval); + /* Numeric range. */ + if (look_ahead == '.') { + /* Brute-force parsing. */ + if (ip_match_next_token(&cp, &saved_cp, &oval) == '.' + && ip_match_next_token(&cp, &saved_cp, &oval) + == IP_MATCH_CODE_OVAL + && saved_oval <= oval) { + VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_RANGE); + VSTRING_ADDCH(byte_codes, saved_oval); + VSTRING_ADDCH(byte_codes, oval); + look_ahead = + ip_match_next_token(&cp, &saved_cp, &oval); + } else { + ipmatch_print_parse_error(byte_codes, pattern, + saved_cp, cp, + "numeric range error"); + return (STR(byte_codes)); + } + } + /* Single number. */ + else { + VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OVAL); + VSTRING_ADDCH(byte_codes, saved_oval); + } + /* Require "," or end-of-wildcard. */ + token_type = look_ahead; + if (token_type == ',') { + continue; + } else if (token_type == IP_MATCH_CODE_CLOSE) { + break; + } else { + ipmatch_print_parse_error(byte_codes, pattern, + saved_cp, cp, + "need \",\" or \"%c\"", + IP_MATCH_CODE_CLOSE); + return (STR(byte_codes)); + } + } else { + ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp, + "need decimal number 0..255"); + return (STR(byte_codes)); + } + } + VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_CLOSE); + break; + + /* + * Invalid field. + */ + default: + ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp, + "need decimal number 0..255 or \"%c\"", + IP_MATCH_CODE_OPEN); + return (STR(byte_codes)); + } + octet_count += 1; + + /* + * Require four address fields. Not one more, not one less. + */ + if (octet_count == 4) { + if (*cp != 0) { + (void) ip_match_next_token(&cp, &saved_cp, &oval); + ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp, + "garbage after pattern"); + return (STR(byte_codes)); + } + VSTRING_ADDCH(byte_codes, 0); + return (0); + } + + /* + * Require "." before the next address field. + */ + if (ip_match_next_token(&cp, &saved_cp, &oval) != '.') { + ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp, + "need \".\""); + return (STR(byte_codes)); + } + } +} + +#ifdef TEST + + /* + * Dummy main program for regression tests. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + VSTRING *byte_codes = vstring_alloc(100); + VSTRING *line_buf = vstring_alloc(100); + char *bufp; + char *err; + char *user_pattern; + char *user_address; + int echo_input = !isatty(0); + + /* + * Iterate over the input stream. The input format is a pattern, followed + * by optional addresses to match against. + */ + while (vstring_fgets_nonl(line_buf, VSTREAM_IN)) { + bufp = STR(line_buf); + if (echo_input) { + vstream_printf("> %s\n", bufp); + vstream_fflush(VSTREAM_OUT); + } + if (*bufp == '#') + continue; + if ((user_pattern = mystrtok(&bufp, " \t")) == 0) + continue; + + /* + * Parse and dump the pattern. + */ + if ((err = ip_match_parse(byte_codes, user_pattern)) != 0) { + vstream_printf("Error: %s\n", err); + } else { + vstream_printf("Code: %s\n", + ip_match_dump(line_buf, STR(byte_codes))); + } + vstream_fflush(VSTREAM_OUT); + + /* + * Match the optional patterns. + */ + while ((user_address = mystrtok(&bufp, " \t")) != 0) { + struct in_addr netw_addr; + + switch (inet_pton(AF_INET, user_address, &netw_addr)) { + case 1: + vstream_printf("Match %s: %s\n", user_address, + ip_match_execute(STR(byte_codes), + (char *) &netw_addr.s_addr) ? + "yes" : "no"); + break; + case 0: + vstream_printf("bad address syntax: %s\n", user_address); + break; + case -1: + vstream_printf("%s: %m\n", user_address); + break; + } + vstream_fflush(VSTREAM_OUT); + } + } + vstring_free(line_buf); + vstring_free(byte_codes); + exit(0); +} + +#endif diff --git a/postfix/src/util/ip_match.h b/postfix/src/util/ip_match.h new file mode 100644 index 000000000..781a04a08 --- /dev/null +++ b/postfix/src/util/ip_match.h @@ -0,0 +1,38 @@ +#ifndef _IP_MATCH_H_INCLUDED_ +#define _IP_MATCH_H_INCLUDED_ + +/*++ +/* NAME +/* ip_match 3h +/* SUMMARY +/* IP address pattern matching +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern char *ip_match_parse(VSTRING *, char *); +extern char *ip_match_save(const VSTRING *); +extern char *ip_match_dump(VSTRING *, const char *); +extern int ip_match_execute(const char *, 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 +/*--*/ + +#endif diff --git a/postfix/src/util/ip_match.in b/postfix/src/util/ip_match.in new file mode 100644 index 000000000..97964fbfb --- /dev/null +++ b/postfix/src/util/ip_match.in @@ -0,0 +1,21 @@ +1.2.3.4 +1.2.300.4 +1.2.3. +1.2.3 +a +1.2.3,4 +1.2.[3].4 +1.2.[].4 +1.2.[.4 +1.2.].4 +1.2.[1..127,128..255].5 +1.2.[1-255].5 +1.2.[1..127.128..255].5 +1.2.3.[4] +1.2.3.[4..1] +1.2.3.[4.1] +1.2.3.[4.x] +1.2.3.[x] +1.2.3.4x +1.2.[3..11].5 1.2.3.5 1.2.2.5 1.2.11.5 1.2.12.5 1.2.11.6 +1.2.[3,5,7,9,11].5 1.2.3.5 1.2.2.5 1.2.4.5 1.2.11.5 1.2.12.5 1.2.11.6 diff --git a/postfix/src/util/ip_match.ref b/postfix/src/util/ip_match.ref new file mode 100644 index 000000000..ba75840c5 --- /dev/null +++ b/postfix/src/util/ip_match.ref @@ -0,0 +1,53 @@ +> 1.2.3.4 +Code: 1.2.3.4 +> 1.2.300.4 +Error: need decimal number 0..255 or "[" at "1.2.>300<.4" +> 1.2.3. +Error: need decimal number 0..255 or "[" at "1.2.3.><" +> 1.2.3 +Error: need "." at "1.2.3><" +> a +Error: need decimal number 0..255 or "[" at ">a<" +> 1.2.3,4 +Error: need "." at "1.2.3>,<4" +> 1.2.[3].4 +Code: 1.2.[3].4 +> 1.2.[].4 +Error: need decimal number 0..255 at "1.2.[>]<.4" +> 1.2.[.4 +Error: need decimal number 0..255 at "1.2.[>.<4" +> 1.2.].4 +Error: need decimal number 0..255 or "[" at "1.2.>]<.4" +> 1.2.[1..127,128..255].5 +Code: 1.2.[1..127,128..255].5 +> 1.2.[1-255].5 +Error: need "," or "]" at "1.2.[1>-<255].5" +> 1.2.[1..127.128..255].5 +Error: need "," or "]" at "1.2.[1..127>.<128..255].5" +> 1.2.3.[4] +Code: 1.2.3.[4] +> 1.2.3.[4..1] +Error: numeric range error at "1.2.3.[4..>1<]" +> 1.2.3.[4.1] +Error: numeric range error at "1.2.3.[4.>1<]" +> 1.2.3.[4.x] +Error: numeric range error at "1.2.3.[4.>x<]" +> 1.2.3.[x] +Error: need decimal number 0..255 at "1.2.3.[>x<]" +> 1.2.3.4x +Error: garbage after pattern at "1.2.3.4>x<" +> 1.2.[3..11].5 1.2.3.5 1.2.2.5 1.2.11.5 1.2.12.5 1.2.11.6 +Code: 1.2.[3..11].5 +Match 1.2.3.5: yes +Match 1.2.2.5: no +Match 1.2.11.5: yes +Match 1.2.12.5: no +Match 1.2.11.6: no +> 1.2.[3,5,7,9,11].5 1.2.3.5 1.2.2.5 1.2.4.5 1.2.11.5 1.2.12.5 1.2.11.6 +Code: 1.2.[3,5,7,9,11].5 +Match 1.2.3.5: yes +Match 1.2.2.5: no +Match 1.2.4.5: no +Match 1.2.11.5: yes +Match 1.2.12.5: no +Match 1.2.11.6: no diff --git a/postfix/src/util/stream_recv_fd.c b/postfix/src/util/stream_recv_fd.c index 185199c9c..5be02e449 100644 --- a/postfix/src/util/stream_recv_fd.c +++ b/postfix/src/util/stream_recv_fd.c @@ -14,7 +14,7 @@ /* /* Arguments: /* .IP fd -/* File descriptor. +/* File descriptor that connects the sending and receiving processes. /* DIAGNOSTICS /* stream_recv_fd() returns -1 upon failure. /* LICENSE diff --git a/postfix/src/util/stream_send_fd.c b/postfix/src/util/stream_send_fd.c index 26116d886..0a9aebf79 100644 --- a/postfix/src/util/stream_send_fd.c +++ b/postfix/src/util/stream_send_fd.c @@ -15,9 +15,9 @@ /* /* Arguments: /* .IP fd -/* File descriptor. +/* File descriptor that connects the sending and receiving processes. /* .IP sendfd -/* Another file descriptor. +/* The file descriptor to be sent. /* DIAGNOSTICS /* stream_send_fd() returns -1 upon failure. /* LICENSE diff --git a/postfix/src/util/unix_recv_fd.c b/postfix/src/util/unix_recv_fd.c index cb6c8aaff..33ab387ca 100644 --- a/postfix/src/util/unix_recv_fd.c +++ b/postfix/src/util/unix_recv_fd.c @@ -14,7 +14,7 @@ /* /* Arguments: /* .IP fd -/* File descriptor. +/* File descriptor that connects the sending and receiving processes. /* DIAGNOSTICS /* unix_recv_fd() returns -1 upon failure. /* LICENSE diff --git a/postfix/src/util/unix_send_fd.c b/postfix/src/util/unix_send_fd.c index bb6bd1282..8cbade120 100644 --- a/postfix/src/util/unix_send_fd.c +++ b/postfix/src/util/unix_send_fd.c @@ -15,9 +15,9 @@ /* /* Arguments: /* .IP fd -/* File descriptor. +/* File descriptor that connects the sending and receiving processes. /* .IP sendfd -/* Another file descriptor. +/* The file descriptor to be sent. /* DIAGNOSTICS /* unix_send_fd() returns -1 upon failure. /* LICENSE