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
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.
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
====================================
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
======================================
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
(default: ${stress?10}${stress:300}s)</b></DT><DD>
<p> The command "read" time limit for <a href="postscreen.8.html">postscreen(8)</a>'s built-in SMTP
-protocol engine. </p>
+protocol engine. This bounds the time to receive an entire command.
+</p>
<p> This feature is available in Postfix 2.8. </p>
<dd>Reject the request when the reversed client network address is
listed with the A record "<i>d.d.d.d</i>" under <i>rbl_domain</i>
-(Postfix version 2.1 and later only). If no "<i>=d.d.d.d</i>" is
-specified, reject the request when the reversed client network
-address is listed with any A record under <i>rbl_domain</i>. <br>
+(Postfix version 2.1 and later only). Each "<i>d</i>" 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 "<i>=d.d.d.d</i>" is specified, reject the request when the
+reversed client network address is listed with any A record under
+<i>rbl_domain</i>. <br>
The <a href="postconf.5.html#maps_rbl_reject_code">maps_rbl_reject_code</a> parameter specifies the response code for
rejected requests (default: 554), the <a href="postconf.5.html#default_rbl_reply">default_rbl_reply</a> parameter
specifies the default server reply, and the <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> parameter
<dd>Accept the request when the reversed client network address is
listed with the A record "<i>d.d.d.d</i>" under <i>dnswl_domain</i>.
+Each "<i>d</i>" can be a pattern inside "[]" that contains one or
+more comma-separated decimal numbers or number..number ranges.
If no "<i>=d.d.d.d</i>" is specified, accept the request when the
reversed client network address is listed with any A record under
<i>dnswl_domain</i>. <br> For safety, <a href="postconf.5.html#permit_dnswl_client">permit_dnswl_client</a> is silently
result is DEFER_IF_REJECT when whitelist lookup fails. This feature
is available in Postfix 2.8 and later. </dd>
-</dd> <dt><b><a name="reject_rhsbl_client">reject_rhsbl_client <i>rbl_domain=d.d.d.d</i></a></b></dt>
+<dt><b><a name="reject_rhsbl_client">reject_rhsbl_client <i>rbl_domain=d.d.d.d</i></a></b></dt>
<dd>Reject the request when the client hostname is listed with the
A record "<i>d.d.d.d</i>" under <i>rbl_domain</i> (Postfix version
-2.1 and later only). If no "<i>=d.d.d.d</i>" is specified, reject
-the request when the client hostname is listed with
+2.1 and later only). Each "<i>d</i>" 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
+"<i>=d.d.d.d</i>" is specified, reject the request when the client
+hostname is listed with
any A record under <i>rbl_domain</i>. See the <a href="postconf.5.html#reject_rbl_client">reject_rbl_client</a>
description above for additional RBL related configuration parameters.
This feature is available in Postfix 2.0 and later; with Postfix
</dd> <dt><b><a name="permit_rhswl_client">permit_rhswl_client <i>rhswl_domain=d.d.d.d</i></a></b></dt>
<dd>Accept the request when the client hostname is listed with the
-A record "<i>d.d.d.d</i>" under <i>rhswl_domain</i>. If no
+A record "<i>d.d.d.d</i>" under <i>rhswl_domain</i>. Each "<i>d</i>"
+can be a pattern inside "[]" that contains one or more comma-separated
+decimal numbers or number..number ranges. If no
"<i>=d.d.d.d</i>" is specified, accept the request when the client
hostname is listed with any A record under <i>rhswl_domain</i>.
<br> Caution: client name whitelisting is fragile, since the client
<dd>Reject the request when the unverified reverse client hostname
is listed with the A record "<i>d.d.d.d</i>" under <i>rbl_domain</i>.
+Each "<i>d</i>" can be a pattern inside "[]" that contains one or
+more comma-separated decimal numbers or number..number ranges.
If no "<i>=d.d.d.d</i>" is specified, reject the request when the
unverified reverse client hostname is listed with any A record under
<i>rbl_domain</i>. See the <a href="postconf.5.html#reject_rbl_client">reject_rbl_client</a> description above for
<dd>Reject the request when the HELO or EHLO hostname hostname is
listed with the A record "<i>d.d.d.d</i>" under <i>rbl_domain</i>
-(Postfix version 2.1 and later only). If no "<i>=d.d.d.d</i>" is
+(Postfix version 2.1 and later only). Each "<i>d</i>" 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 "<i>=d.d.d.d</i>" is
specified, reject the request when the HELO or EHLO hostname is
listed with any A record under <i>rbl_domain</i>. See the
<a href="postconf.5.html#reject_rbl_client">reject_rbl_client</a> description for additional RBL related configuration
<dd>Reject the request when the RCPT TO domain is listed with the
A record "<i>d.d.d.d</i>" under <i>rbl_domain</i> (Postfix version
-2.1 and later only). If no "<i>=d.d.d.d</i>" is specified, reject
+2.1 and later only). Each "<i>d</i>" 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
+"<i>=d.d.d.d</i>" is specified, reject
the request when the RCPT TO domain is listed with
any A record under <i>rbl_domain</i>. <br> The <a href="postconf.5.html#maps_rbl_reject_code">maps_rbl_reject_code</a>
parameter specifies the response code for rejected requests (default:
<dd>Reject the request when the MAIL FROM domain is listed with
the A record "<i>d.d.d.d</i>" under <i>rbl_domain</i> (Postfix
-version 2.1 and later only). If no "<i>=d.d.d.d</i>" is specified,
+version 2.1 and later only). Each "<i>d</i>" 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
+"<i>=d.d.d.d</i>" is specified,
reject the request when the MAIL FROM domain is
listed with any A record under <i>rbl_domain</i>. <br> The
<a href="postconf.5.html#maps_rbl_reject_code">maps_rbl_reject_code</a> parameter specifies the response code for
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)
.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
.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.
.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
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
.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
.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
.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
.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
<dd>Reject the request when the reversed client network address is
listed with the A record "<i>d.d.d.d</i>" under <i>rbl_domain</i>
-(Postfix version 2.1 and later only). If no "<i>=d.d.d.d</i>" is
-specified, reject the request when the reversed client network
-address is listed with any A record under <i>rbl_domain</i>. <br>
+(Postfix version 2.1 and later only). Each "<i>d</i>" 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 "<i>=d.d.d.d</i>" is specified, reject the request when the
+reversed client network address is listed with any A record under
+<i>rbl_domain</i>. <br>
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
<dd>Accept the request when the reversed client network address is
listed with the A record "<i>d.d.d.d</i>" under <i>dnswl_domain</i>.
+Each "<i>d</i>" can be a pattern inside "[]" that contains one or
+more comma-separated decimal numbers or number..number ranges.
If no "<i>=d.d.d.d</i>" is specified, accept the request when the
reversed client network address is listed with any A record under
<i>dnswl_domain</i>. <br> For safety, permit_dnswl_client is silently
<dd>Reject the request when the client hostname is listed with the
A record "<i>d.d.d.d</i>" under <i>rbl_domain</i> (Postfix version
-2.1 and later only). If no "<i>=d.d.d.d</i>" is specified, reject
-the request when the client hostname is listed with
+2.1 and later only). Each "<i>d</i>" 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
+"<i>=d.d.d.d</i>" is specified, reject the request when the client
+hostname is listed with
any A record under <i>rbl_domain</i>. 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
</dd> <dt><b><a name="permit_rhswl_client">permit_rhswl_client <i>rhswl_domain=d.d.d.d</i></a></b></dt>
<dd>Accept the request when the client hostname is listed with the
-A record "<i>d.d.d.d</i>" under <i>rhswl_domain</i>. If no
+A record "<i>d.d.d.d</i>" under <i>rhswl_domain</i>. Each "<i>d</i>"
+can be a pattern inside "[]" that contains one or more comma-separated
+decimal numbers or number..number ranges. If no
"<i>=d.d.d.d</i>" is specified, accept the request when the client
hostname is listed with any A record under <i>rhswl_domain</i>.
<br> Caution: client name whitelisting is fragile, since the client
<dd>Reject the request when the unverified reverse client hostname
is listed with the A record "<i>d.d.d.d</i>" under <i>rbl_domain</i>.
+Each "<i>d</i>" can be a pattern inside "[]" that contains one or
+more comma-separated decimal numbers or number..number ranges.
If no "<i>=d.d.d.d</i>" is specified, reject the request when the
unverified reverse client hostname is listed with any A record under
<i>rbl_domain</i>. See the reject_rbl_client description above for
<dd>Reject the request when the HELO or EHLO hostname hostname is
listed with the A record "<i>d.d.d.d</i>" under <i>rbl_domain</i>
-(Postfix version 2.1 and later only). If no "<i>=d.d.d.d</i>" is
+(Postfix version 2.1 and later only). Each "<i>d</i>" 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 "<i>=d.d.d.d</i>" is
specified, reject the request when the HELO or EHLO hostname is
listed with any A record under <i>rbl_domain</i>. See the
reject_rbl_client description for additional RBL related configuration
<dd>Reject the request when the RCPT TO domain is listed with the
A record "<i>d.d.d.d</i>" under <i>rbl_domain</i> (Postfix version
-2.1 and later only). If no "<i>=d.d.d.d</i>" is specified, reject
+2.1 and later only). Each "<i>d</i>" 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
+"<i>=d.d.d.d</i>" is specified, reject
the request when the RCPT TO domain is listed with
any A record under <i>rbl_domain</i>. <br> The maps_rbl_reject_code
parameter specifies the response code for rejected requests (default:
<dd>Reject the request when the MAIL FROM domain is listed with
the A record "<i>d.d.d.d</i>" under <i>rbl_domain</i> (Postfix
-version 2.1 and later only). If no "<i>=d.d.d.d</i>" is specified,
+version 2.1 and later only). Each "<i>d</i>" 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
+"<i>=d.d.d.d</i>" is specified,
reject the request when the MAIL FROM domain is
listed with any A record under <i>rbl_domain</i>. <br> The
maps_rbl_reject_code parameter specifies the response code for
%PARAM postscreen_command_time_limit ${stress?10}${stress:300}s
<p> The command "read" time limit for postscreen(8)'s built-in SMTP
-protocol engine. </p>
+protocol engine. This bounds the time to receive an entire command.
+</p>
<p> This feature is available in Postfix 2.8. </p>
* 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
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
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
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
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
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
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
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
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
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
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
/* System library. */
#include <sys_defs.h>
+#include <sys/socket.h> /* AF_INET */
+#include <netinet/in.h> /* inet_pton() */
+#include <arpa/inet.h> /* inet_pton() */
#include <stdio.h> /* sscanf */
/* Utility library. */
#include <connect.h>
#include <split_at.h>
#include <valid_hostname.h>
+#include <ip_match.h>
+#include <myaddrinfo.h>
/* Global library. */
{
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
} 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\"",
* 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);
}
* 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"));
}
/*
- * 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;
- }
}
/*
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;
- }
}
}
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.
*/
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
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
#include <myaddrinfo.h>
#include <sock_addr.h>
#include <inet_proto.h>
+#include <split_at.h>
/* Global library. */
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
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
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
* 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");
}
/* 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++;
#include <attr_clnt.h>
#include <myaddrinfo.h>
#include <inet_proto.h>
+#include <ip_match.h>
/* DNS library. */
*/
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.
*/
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 */
* 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
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;
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);
}
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,
}
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);
/*
* Clean up.
*/
+ myfree(rbl_exp.domain);
vstring_free(why);
return (result);
/* 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);
}
int i;
SMTPD_RBL_STATE *rbl;
const char *reply_addr;
+ const char *byte_codes;
struct addrinfo *res;
unsigned char *ipv6_addr;
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);
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
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);
#include <myaddrinfo.h>
#include <sock_addr.h>
#include <inet_proto.h>
+#include <split_at.h>
/* Global library. */
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
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 \
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 \
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)
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
@shar $(FILES)
lint:
- lint $(SRCS)
+ lint $(DEFS) $(SRCS) $(LINTFIX)
clean:
rm -f *.o $(LIB) *core $(TESTPROG) junk $(MAKES) *.tmp
$(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:
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.in >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.in >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 \
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
--- /dev/null
+/*++
+/* NAME
+/* ip_lmatch 3
+/* SUMMARY
+/* lazy IP address pattern matching
+/* SYNOPSIS
+/* #include <ip_lmatch.h>
+/*
+/* 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 <sys_defs.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <myaddrinfo.h>
+#include <ip_lmatch.h>
+
+ /*
+ * 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 <sys/socket.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+
+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
--- /dev/null
+#ifndef _IP_LMATCH_H_INCLUDED_
+#define _IP_LMATCH_H_INCLUDED_
+
+/*++
+/* NAME
+/* ip_lmatch 3h
+/* SUMMARY
+/* lazy IP address pattern matching
+/* SYNOPSIS
+/* #include <ip_lmatch.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * 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
--- /dev/null
+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
--- /dev/null
+> 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
--- /dev/null
+/*++
+/* NAME
+/* ip_match 3
+/* SUMMARY
+/* IP address pattern matching
+/* SYNOPSIS
+/* #include <ip_match.h>
+/*
+/* 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 <sys_defs.h>
+#include <sys/socket.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <ip_match.h>
+
+ /*
+ * 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 <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+
+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
--- /dev/null
+#ifndef _IP_MATCH_H_INCLUDED_
+#define _IP_MATCH_H_INCLUDED_
+
+/*++
+/* NAME
+/* ip_match 3h
+/* SUMMARY
+/* IP address pattern matching
+/* SYNOPSIS
+/* #include <ip_match.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * 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
--- /dev/null
+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
--- /dev/null
+> 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
/*
/* Arguments:
/* .IP fd
-/* File descriptor.
+/* File descriptor that connects the sending and receiving processes.
/* DIAGNOSTICS
/* stream_recv_fd() returns -1 upon failure.
/* LICENSE
/*
/* 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
/*
/* Arguments:
/* .IP fd
-/* File descriptor.
+/* File descriptor that connects the sending and receiving processes.
/* DIAGNOSTICS
/* unix_recv_fd() returns -1 upon failure.
/* LICENSE
/*
/* 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