diff --git a/postfix/HISTORY b/postfix/HISTORY index 4d58b31ef..617174d71 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -22316,3 +22316,14 @@ Apologies for any names omitted. Cleanup: existing if/endif support for pcre and regexp tables, in preparation for new if/endif support for cidr tables. Files: util/dict_regexp.c, util/dict_pcre.c. + +20160526 + + Feature: cidr tables now support if/endif and negation (by + prepending "!" to a pattern), just like regexp and pcre + tables. The primarily purpose is to improve readability of + complex tables. Files: util/cidr_match.[hc], util/dict_cidr.c, + proto/cidr_table. + + Cleanup: make regexp: and pcre: parser warning messages more + similar. Files: dict_regexp.c, dict_pcre.c. diff --git a/postfix/RELEASE_NOTES b/postfix/RELEASE_NOTES index 117bb6b28..94fab09e3 100644 --- a/postfix/RELEASE_NOTES +++ b/postfix/RELEASE_NOTES @@ -15,3 +15,12 @@ specifies the release date of a stable release or snapshot release. If you upgrade from Postfix 3.0 or earlier, read RELEASE_NOTES-3.1 before proceeding. + +Major changes with snapshot 20160527 +==================================== + +Postfix cidr tables now support if..endif, and pattern negation +with "!", just like regexp and pcre tables. The if..endif can speed +up lookups by skipping over irrelevant patterns, and can make rule +maintenance easier because rules for a network can now be placed +inside if..endif. See the cidr_table(5) manpage for syntax details. diff --git a/postfix/html/cidr_table.5.html b/postfix/html/cidr_table.5.html index 67b6ceb6c..c8f63f9da 100644 --- a/postfix/html/cidr_table.5.html +++ b/postfix/html/cidr_table.5.html @@ -30,30 +30,39 @@ CIDR_TABLE(5) CIDR_TABLE(5) TABLE FORMAT The general form of a Postfix CIDR table is: - network_address/network_mask result - When a search string matches the specified network block, use - the corresponding result value. Specify 0.0.0.0/0 to match every - IPv4 address, and ::/0 to match every IPv6 address. + pattern result + When a search string matches the specified pattern, use the cor- + responding result value. The pattern must be in network/prefix + or network_address form (see ADDRESS PATTERN SYNTAX below). - An IPv4 network address is a sequence of four decimal octets - separated by ".", and an IPv6 network address is a sequence of - three to eight hexadecimal octet pairs separated by ":". + !pattern result + When a search string does not match the specified pattern, use + the specified result value. The pattern must be in network/pre- + fix or network_address form (see ADDRESS PATTERN SYNTAX below). - The network_mask is the number of high-order bits in the net- - work_address that the search string must match. + This feature is available in Postfix 3.2 and later. - Before comparisons are made, lookup keys and table entries are - converted from string to binary. Therefore table entries will be - matched regardless of redundant zero characters. + if pattern - Note: address information may be enclosed inside "[]" but this - form is not required. + endif When a search string matches the specified pattern, match that + search string against the patterns between if and endif. The + pattern must be in network/prefix or network_address form (see + ADDRESS PATTERN SYNTAX below). The if..endif can nest. - IPv6 support is available in Postfix 2.2 and later. + Note: do not prepend whitespace to text between if..endif. - network_address result - When a search string matches the specified network address, use - the corresponding result value. + This feature is available in Postfix 3.2 and later. + + if !pattern + + endif When a search string does not match the specified pattern, match + that search string against the patterns between if and endif. + The pattern must be in network/prefix or network_address form + (see ADDRESS PATTERN SYNTAX below). The if..endif can nest. + + Note: do not prepend whitespace to text between if..endif. + + This feature is available in Postfix 3.2 and later. blank lines and comments Empty lines and whitespace-only lines are ignored, as are lines @@ -67,6 +76,27 @@ CIDR_TABLE(5) CIDR_TABLE(5) Patterns are applied in the order as specified in the table, until a pattern is found that matches the search string. +ADDRESS PATTERN SYNTAX + Postfix CIDR tables are pattern-based. A pattern is either a net- + work_address which requires an exact match, or a network_address/pre- + fix_length where the prefix_length part specifies the length of the + network_address prefix that must be matched (the other bits in the net- + work_address part must be zero). + + An IPv4 network address is a sequence of four decimal octets separated + by ".", and an IPv6 network address is a sequence of three to eight + hexadecimal octets or octet pairs separated by ":". The pattern + 0.0.0.0/0 matches every IPv4 address, and ::/0 matches every IPv6 + address. IPv6 support is available in Postfix 2.2 and later. + + Before comparisons are made, lookup keys and table entries are con- + verted from string to binary. Therefore, IPv6 patterns will be matched + regardless of leading zeros (a leading zero in an IPv4 address octet + indicates octal notation). + + Note: address information may be enclosed inside "[]" but this form is + not required. + EXAMPLE SMTPD ACCESS MAP /etc/postfix/main.cf: smtpd_client_restrictions = ... cidr:/etc/postfix/client.cidr ... diff --git a/postfix/man/man5/cidr_table.5 b/postfix/man/man5/cidr_table.5 index b199e39ae..51a58a9d4 100644 --- a/postfix/man/man5/cidr_table.5 +++ b/postfix/man/man5/cidr_table.5 @@ -33,30 +33,42 @@ described in the SYNOPSIS above. .ad .fi The general form of a Postfix CIDR table is: -.IP "\fInetwork_address\fB/\fInetwork_mask result\fR" -When a search string matches the specified network block, -use the corresponding \fIresult\fR value. Specify -0.0.0.0/0 to match every IPv4 address, and ::/0 to match -every IPv6 address. - -An IPv4 network address is a sequence of four decimal octets -separated by ".", and an IPv6 network address is a sequence -of three to eight hexadecimal octet pairs separated by ":". - -The \fInetwork_mask\fR is the number of high\-order bits in -the \fInetwork_address\fR that the search string must match. - -Before comparisons are made, lookup keys and table entries -are converted from string to binary. Therefore table entries -will be matched regardless of redundant zero characters. - -Note: address information may be enclosed inside "[]" but -this form is not required. - -IPv6 support is available in Postfix 2.2 and later. -.IP "\fInetwork_address result\fR" -When a search string matches the specified network address, -use the corresponding \fIresult\fR value. +.IP "\fIpattern result\fR" +When a search string matches the specified \fIpattern\fR, use +the corresponding \fIresult\fR value. The \fIpattern\fR must be +in \fInetwork/prefix\fR or \fInetwork_address\fR form (see +ADDRESS PATTERN SYNTAX below). +.IP "\fB!\fIpattern result\fR" +When a search string does not match the specified \fIpattern\fR, +use the specified \fIresult\fR value. The \fIpattern\fR must +be in \fInetwork/prefix\fR or \fInetwork_address\fR form (see +ADDRESS PATTERN SYNTAX below). +.sp +This feature is available in Postfix 3.2 and later. +.IP "\fBif \fIpattern\fR" +.IP "\fBendif\fR" +When a search string matches the specified \fIpattern\fR, match +that search string against the patterns between \fBif\fR and +\fBendif\fR. The \fIpattern\fR must be in \fInetwork/prefix\fR or +\fInetwork_address\fR form (see ADDRESS PATTERN SYNTAX below). The +\fBif\fR..\fBendif\fR can nest. +.sp +Note: do not prepend whitespace to text between +\fBif\fR..\fBendif\fR. +.sp +This feature is available in Postfix 3.2 and later. +.IP "\fBif !\fIpattern\fR" +.IP "\fBendif\fR" +When a search string does not match the specified \fIpattern\fR, +match that search string against the patterns between \fBif\fR and +\fBendif\fR. The \fIpattern\fR must be in \fInetwork/prefix\fR or +\fInetwork_address\fR form (see ADDRESS PATTERN SYNTAX below). The +\fBif\fR..\fBendif\fR can nest. +.sp +Note: do not prepend whitespace to text between +\fBif\fR..\fBendif\fR. +.sp +This feature is available in Postfix 3.2 and later. .IP "blank lines and comments" Empty lines and whitespace\-only lines are ignored, as are lines whose first non\-whitespace character is a `#'. @@ -70,6 +82,32 @@ starts with whitespace continues a logical line. .fi Patterns are applied in the order as specified in the table, until a pattern is found that matches the search string. +.SH "ADDRESS PATTERN SYNTAX" +.na +.nf +.ad +.fi +Postfix CIDR tables are pattern\-based. A pattern is either +a \fInetwork_address\fR which requires an exact match, or a +\fInetwork_address/prefix_length\fR where the \fIprefix_length\fR +part specifies the length of the \fInetwork_address\fR prefix +that must be matched (the other bits in the \fInetwork_address\fR +part must be zero). + +An IPv4 network address is a sequence of four decimal octets +separated by ".", and an IPv6 network address is a sequence +of three to eight hexadecimal octets or octet pairs separated by +":". The pattern 0.0.0.0/0 matches every IPv4 address, and ::/0 +matches every IPv6 address. IPv6 support is available in +Postfix 2.2 and later. + +Before comparisons are made, lookup keys and table entries +are converted from string to binary. Therefore, IPv6 patterns +will be matched regardless of leading zeros (a leading zero in +an IPv4 address octet indicates octal notation). + +Note: address information may be enclosed inside "[]" but +this form is not required. .SH "EXAMPLE SMTPD ACCESS MAP" .na .nf diff --git a/postfix/proto/cidr_table b/postfix/proto/cidr_table index 4787a79d3..51d39aa13 100644 --- a/postfix/proto/cidr_table +++ b/postfix/proto/cidr_table @@ -25,30 +25,42 @@ # .ad # .fi # The general form of a Postfix CIDR table is: -# .IP "\fInetwork_address\fB/\fInetwork_mask result\fR" -# When a search string matches the specified network block, -# use the corresponding \fIresult\fR value. Specify -# 0.0.0.0/0 to match every IPv4 address, and ::/0 to match -# every IPv6 address. -# -# An IPv4 network address is a sequence of four decimal octets -# separated by ".", and an IPv6 network address is a sequence -# of three to eight hexadecimal octet pairs separated by ":". -# -# The \fInetwork_mask\fR is the number of high-order bits in -# the \fInetwork_address\fR that the search string must match. -# -# Before comparisons are made, lookup keys and table entries -# are converted from string to binary. Therefore table entries -# will be matched regardless of redundant zero characters. -# -# Note: address information may be enclosed inside "[]" but -# this form is not required. -# -# IPv6 support is available in Postfix 2.2 and later. -# .IP "\fInetwork_address result\fR" -# When a search string matches the specified network address, -# use the corresponding \fIresult\fR value. +# .IP "\fIpattern result\fR" +# When a search string matches the specified \fIpattern\fR, use +# the corresponding \fIresult\fR value. The \fIpattern\fR must be +# in \fInetwork/prefix\fR or \fInetwork_address\fR form (see +# ADDRESS PATTERN SYNTAX below). +# .IP "\fB!\fIpattern result\fR" +# When a search string does not match the specified \fIpattern\fR, +# use the specified \fIresult\fR value. The \fIpattern\fR must +# be in \fInetwork/prefix\fR or \fInetwork_address\fR form (see +# ADDRESS PATTERN SYNTAX below). +# .sp +# This feature is available in Postfix 3.2 and later. +# .IP "\fBif \fIpattern\fR" +# .IP "\fBendif\fR" +# When a search string matches the specified \fIpattern\fR, match +# that search string against the patterns between \fBif\fR and +# \fBendif\fR. The \fIpattern\fR must be in \fInetwork/prefix\fR or +# \fInetwork_address\fR form (see ADDRESS PATTERN SYNTAX below). The +# \fBif\fR..\fBendif\fR can nest. +# .sp +# Note: do not prepend whitespace to text between +# \fBif\fR..\fBendif\fR. +# .sp +# This feature is available in Postfix 3.2 and later. +# .IP "\fBif !\fIpattern\fR" +# .IP "\fBendif\fR" +# When a search string does not match the specified \fIpattern\fR, +# match that search string against the patterns between \fBif\fR and +# \fBendif\fR. The \fIpattern\fR must be in \fInetwork/prefix\fR or +# \fInetwork_address\fR form (see ADDRESS PATTERN SYNTAX below). The +# \fBif\fR..\fBendif\fR can nest. +# .sp +# Note: do not prepend whitespace to text between +# \fBif\fR..\fBendif\fR. +# .sp +# This feature is available in Postfix 3.2 and later. # .IP "blank lines and comments" # Empty lines and whitespace-only lines are ignored, as # are lines whose first non-whitespace character is a `#'. @@ -60,6 +72,30 @@ # .fi # Patterns are applied in the order as specified in the table, until a # pattern is found that matches the search string. +# ADDRESS PATTERN SYNTAX +# .ad +# .fi +# Postfix CIDR tables are pattern-based. A pattern is either +# a \fInetwork_address\fR which requires an exact match, or a +# \fInetwork_address/prefix_length\fR where the \fIprefix_length\fR +# part specifies the length of the \fInetwork_address\fR prefix +# that must be matched (the other bits in the \fInetwork_address\fR +# part must be zero). +# +# An IPv4 network address is a sequence of four decimal octets +# separated by ".", and an IPv6 network address is a sequence +# of three to eight hexadecimal octets or octet pairs separated by +# ":". The pattern 0.0.0.0/0 matches every IPv4 address, and ::/0 +# matches every IPv6 address. IPv6 support is available in +# Postfix 2.2 and later. +# +# Before comparisons are made, lookup keys and table entries +# are converted from string to binary. Therefore, IPv6 patterns +# will be matched regardless of leading zeros (a leading zero in +# an IPv4 address octet indicates octal notation). +# +# Note: address information may be enclosed inside "[]" but +# this form is not required. # EXAMPLE SMTPD ACCESS MAP # .nf # /etc/postfix/main.cf: diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index c5e9d7346..00b7d7b41 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 "20160522" +#define MAIL_RELEASE_DATE "20160527" #define MAIL_VERSION_NUMBER "3.2" #ifdef SNAPSHOT diff --git a/postfix/src/util/cidr_match.c b/postfix/src/util/cidr_match.c index 387c02f62..7a921d75f 100644 --- a/postfix/src/util/cidr_match.c +++ b/postfix/src/util/cidr_match.c @@ -14,6 +14,14 @@ /* int cidr_match_execute(info, address) /* CIDR_MATCH *info; /* const char *address; +/* AUXILIARY FUNCTIONS +/* VSTRING *cidr_match_parse_if(info, address, why) +/* CIDR_MATCH *info; +/* char *address; +/* VSTRING *why; +/* +/* void cidr_match_endif(info) +/* CIDR_MATCH *info; /* DESCRIPTION /* This module parses address or address/length patterns and /* provides simple address matching. The implementation is @@ -29,6 +37,12 @@ /* (the caller should give the latter to vstring_free()). /* The pattern argument is destroyed. /* +/* cidr_match_parse_if() parses the address that follows an IF +/* token, and stores the result into the info argument. +/* +/* cidr_match_endif() handles the occurrence of an ENDIF token, +/* and updates the info argument. +/* /* cidr_match_execute() matches the specified address against /* a list of parsed expressions, and returns the matching /* expression's data structure. @@ -39,6 +53,11 @@ /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA /*--*/ /* System library. */ @@ -88,15 +107,44 @@ (msg_panic("%s: bad address family %d", myname, (f)), 0)) #endif +/* cidr_match_entry - match one entry */ + +static inline int cidr_match_entry(CIDR_MATCH *entry, + unsigned char *addr_bytes) +{ + unsigned char *mp; + unsigned char *np; + unsigned char *ap; + + /* Unoptimized case: netmask with some or all bits zero. */ + if (entry->mask_shift < entry->addr_bit_count) { + for (np = entry->net_bytes, mp = entry->mask_bytes, + ap = addr_bytes; /* void */ ; np++, mp++, ap++) { + if (ap >= addr_bytes + entry->addr_byte_count) + return (entry->match); + if ((*ap & *mp) != *np) + break; + } + } + /* Optimized case: all 1 netmask (i.e. no netmask specified). */ + else { + for (np = entry->net_bytes, + ap = addr_bytes; /* void */ ; np++, ap++) { + if (ap >= addr_bytes + entry->addr_byte_count) + return (entry->match); + if (*ap != *np) + break; + } + } + return (!entry->match); +} + /* cidr_match_execute - match address against compiled CIDR pattern list */ CIDR_MATCH *cidr_match_execute(CIDR_MATCH *list, const char *addr) { unsigned char addr_bytes[CIDR_MATCH_ABYTES]; unsigned addr_family; - unsigned char *mp; - unsigned char *np; - unsigned char *ap; CIDR_MATCH *entry; addr_family = CIDR_MATCH_ADDR_FAMILY(addr); @@ -104,27 +152,26 @@ CIDR_MATCH *cidr_match_execute(CIDR_MATCH *list, const char *addr) return (0); for (entry = list; entry; entry = entry->next) { - if (entry->addr_family == addr_family) { - /* Unoptimized case: netmask with some or all bits zero. */ - if (entry->mask_shift < entry->addr_bit_count) { - for (np = entry->net_bytes, mp = entry->mask_bytes, - ap = addr_bytes; /* void */ ; np++, mp++, ap++) { - if (ap >= addr_bytes + entry->addr_byte_count) - return (entry); - if ((*ap & *mp) != *np) - break; - } - } - /* Optimized case: all 1 netmask (i.e. no netmask specified). */ - else { - for (np = entry->net_bytes, - ap = addr_bytes; /* void */ ; np++, ap++) { - if (ap >= addr_bytes + entry->addr_byte_count) - return (entry); - if (*ap != *np) - break; - } - } + + switch (entry->op) { + + case CIDR_MATCH_OP_MATCH: + if (entry->addr_family == addr_family) + if (cidr_match_entry(entry, addr_bytes)) + return (entry); + break; + + case CIDR_MATCH_OP_IF: + if (entry->addr_family == addr_family) + if (cidr_match_entry(entry, addr_bytes)) + continue; + /* An IF without matching ENDIF has no end-of block entry. */ + if ((entry = entry->block_end) == 0) + break; + /* FALLTHROUGH */ + + case CIDR_MATCH_OP_ENDIF: + continue; } } return (0); @@ -141,6 +188,23 @@ VSTRING *cidr_match_parse(CIDR_MATCH *ip, char *pattern, VSTRING *why) unsigned char *np; unsigned char *mp; + /* + * Process negation operators. XXX unlike dict_regexp_get_pat() and + * dict_pcre_get_pattern(), dict_cidr_parse_rule() does not allow space + * between ! and the remainder of a pattern. However, those spaces are + * not documented, they were more a helpful thing. + */ + ip->match = 1; + while (*pattern == '!') { + ip->match = !ip->match; + pattern++; + } + + if (*pattern == 0) { + vstring_sprintf(why ? why : (why = vstring_alloc(20)), "no pattern"); + return (why); + } + /* * Strip [] from [addr/len] or [addr]/len, destroying the pattern. CIDR * maps don't need [] to eliminate syntax ambiguity, but matchlists need @@ -224,7 +288,30 @@ VSTRING *cidr_match_parse(CIDR_MATCH *ip, char *pattern, VSTRING *why) /* * Wrap up the result. */ + ip->op = CIDR_MATCH_OP_MATCH; ip->next = 0; + ip->block_end = 0; return (0); } + +/* cidr_match_parse_if - parse CIDR pattern after IF */ + +VSTRING *cidr_match_parse_if(CIDR_MATCH *ip, char *pattern, VSTRING *why) +{ + VSTRING *ret; + + if ((ret = cidr_match_parse(ip, pattern, why)) == 0) + ip->op = CIDR_MATCH_OP_IF; + return (ret); +} + +/* cidr_match_endif - handle ENDIF pattern */ + +void cidr_match_endif(CIDR_MATCH *ip) +{ + memset(ip, 0, sizeof(*ip)); + ip->op = CIDR_MATCH_OP_ENDIF; + ip->next = 0; /* maybe not all bits 0 */ + ip->block_end = 0; +} diff --git a/postfix/src/util/cidr_match.h b/postfix/src/util/cidr_match.h index 153394f80..1fab97671 100644 --- a/postfix/src/util/cidr_match.h +++ b/postfix/src/util/cidr_match.h @@ -38,6 +38,8 @@ * Each parsed CIDR pattern can be member of a linked list. */ typedef struct CIDR_MATCH { + int op; /* operation, match or control flow */ + int match; /* positive or negative match */ unsigned char net_bytes[CIDR_MATCH_ABYTES]; /* network portion */ unsigned char mask_bytes[CIDR_MATCH_ABYTES]; /* network mask */ unsigned char addr_family; /* AF_XXX */ @@ -45,9 +47,17 @@ typedef struct CIDR_MATCH { unsigned char addr_bit_count; /* optimization */ unsigned char mask_shift; /* optimization */ struct CIDR_MATCH *next; /* next entry */ + struct CIDR_MATCH *block_end; /* block terminator */ } CIDR_MATCH; +#define CIDR_MATCH_OP_MATCH 1 /* Match this pattern */ +#define CIDR_MATCH_OP_IF 2 /* Increase if/endif nesting on match */ +#define CIDR_MATCH_OP_ENDIF 3 /* Decrease if/endif nesting on match */ + extern VSTRING *cidr_match_parse(CIDR_MATCH *, char *, VSTRING *); +extern VSTRING *cidr_match_parse_if(CIDR_MATCH *, char *, VSTRING *); +extern void cidr_match_endif(CIDR_MATCH *); + extern CIDR_MATCH *cidr_match_execute(CIDR_MATCH *, const char *); /* LICENSE @@ -59,6 +69,11 @@ extern CIDR_MATCH *cidr_match_execute(CIDR_MATCH *, const char *); /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA /*--*/ #endif diff --git a/postfix/src/util/dict_cidr.c b/postfix/src/util/dict_cidr.c index e14611a73..112582bc6 100644 --- a/postfix/src/util/dict_cidr.c +++ b/postfix/src/util/dict_cidr.c @@ -27,6 +27,11 @@ /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA /*--*/ /* System library. */ @@ -54,6 +59,7 @@ #include #include #include +#include /* Application-specific. */ @@ -63,6 +69,7 @@ typedef struct DICT_CIDR_ENTRY { CIDR_MATCH cidr_info; /* must be first */ char *value; /* lookup result */ + int lineno; } DICT_CIDR_ENTRY; typedef struct { @@ -106,7 +113,8 @@ static void dict_cidr_close(DICT *dict) /* dict_cidr_parse_rule - parse CIDR table rule into network, mask and value */ -static DICT_CIDR_ENTRY *dict_cidr_parse_rule(char *p, VSTRING *why) +static DICT_CIDR_ENTRY *dict_cidr_parse_rule(char *p, int lineno, int nesting, + VSTRING *why) { DICT_CIDR_ENTRY *rule; char *pattern; @@ -115,33 +123,76 @@ static DICT_CIDR_ENTRY *dict_cidr_parse_rule(char *p, VSTRING *why) MAI_HOSTADDR_STR hostaddr; /* - * Split the rule into key and value. We already eliminated leading - * whitespace, comments, empty lines or lines with whitespace only. This - * means a null key can't happen but we will handle this anyway. + * IF must be followed by a pattern. */ - pattern = p; - while (*p && !ISSPACE(*p)) /* Skip over key */ - p++; - if (*p) /* Terminate key */ - *p++ = 0; - while (*p && ISSPACE(*p)) /* Skip whitespace */ - p++; - value = p; - trimblanks(value, 0)[0] = 0; /* Trim trailing blanks */ - if (*pattern == 0) { - vstring_sprintf(why, "no address pattern"); - return (0); - } - if (*value == 0) { - vstring_sprintf(why, "no lookup result"); - return (0); + if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) { + p += 2; + while (*p && ISSPACE(*p)) /* Skip whitespace */ + p++; + if (*p == 0) { + vstring_sprintf(why, "no address pattern"); + return (0); + } + trimblanks(p, 0)[0] = 0; /* Trim trailing blanks */ + if (cidr_match_parse_if(&cidr_info, p, why) != 0) + return (0); + value = ""; } /* - * Parse the pattern, destroying it in the process. + * ENDIF must not be followed by other text. */ - if (cidr_match_parse(&cidr_info, pattern, why) != 0) - return (0); + else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) { + p += 5; + while (*p && ISSPACE(*p)) /* Skip whitespace */ + p++; + if (*p != 0) { + vstring_sprintf(why, "garbage after ENDIF"); + return (0); + } + if (nesting == 0) { + vstring_sprintf(why, "ENDIF without IF"); + return (0); + } + cidr_match_endif(&cidr_info); + value = ""; + } + + /* + * An address pattern. + */ + else { + + /* + * Split the rule into key and value. We already eliminated leading + * whitespace, comments, empty lines or lines with whitespace only. + * This means a null key can't happen but we will handle this anyway. + */ + pattern = p; + while (*p && !ISSPACE(*p)) /* Skip over key */ + p++; + if (*p) /* Terminate key */ + *p++ = 0; + while (*p && ISSPACE(*p)) /* Skip whitespace */ + p++; + value = p; + trimblanks(value, 0)[0] = 0; /* Trim trailing blanks */ + if (*pattern == 0) { + vstring_sprintf(why, "no address pattern"); + return (0); + } + + /* + * Parse the pattern, destroying it in the process. + */ + if (cidr_match_parse(&cidr_info, pattern, why) != 0) + return (0); + + if (*value == 0) { + vstring_sprintf(why, "no lookup result"); + return (0); + } + } /* * Bundle up the result. @@ -149,6 +200,7 @@ static DICT_CIDR_ENTRY *dict_cidr_parse_rule(char *p, VSTRING *why) rule = (DICT_CIDR_ENTRY *) mymalloc(sizeof(DICT_CIDR_ENTRY)); rule->cidr_info = cidr_info; rule->value = mystrdup(value); + rule->lineno = lineno; if (msg_verbose) { if (inet_ntop(cidr_info.addr_family, cidr_info.net_bytes, @@ -164,6 +216,7 @@ static DICT_CIDR_ENTRY *dict_cidr_parse_rule(char *p, VSTRING *why) DICT *dict_cidr_open(const char *mapname, int open_flags, int dict_flags) { + const char myname[] = "dict_cidr_open"; DICT_CIDR *dict_cidr; VSTREAM *map_fp = 0; struct stat st; @@ -173,6 +226,9 @@ DICT *dict_cidr_open(const char *mapname, int open_flags, int dict_flags) DICT_CIDR_ENTRY *last_rule = 0; int last_line = 0; int lineno; + int nesting = 0; + DICT_CIDR_ENTRY **rule_stack = 0; + MVECT mvect; /* * Let the optimizer worry about eliminating redundant code. @@ -225,12 +281,35 @@ DICT *dict_cidr_open(const char *mapname, int open_flags, int dict_flags) dict_cidr->dict.owner.status = (st.st_uid != 0); while (readllines(line_buffer, map_fp, &last_line, &lineno)) { - rule = dict_cidr_parse_rule(vstring_str(line_buffer), why); + rule = dict_cidr_parse_rule(vstring_str(line_buffer), lineno, + nesting, why); if (rule == 0) { msg_warn("cidr map %s, line %d: %s: skipping this rule", mapname, lineno, vstring_str(why)); continue; } + if (rule->cidr_info.op == CIDR_MATCH_OP_IF) { + if (rule_stack == 0) + rule_stack = (DICT_CIDR_ENTRY **) mvect_alloc(&mvect, + sizeof(*rule_stack), nesting + 1, + (MVECT_FN) 0, (MVECT_FN) 0); + else + rule_stack = + (DICT_CIDR_ENTRY **) mvect_realloc(&mvect, nesting + 1); + rule_stack[nesting] = rule; + nesting++; + } else if (rule->cidr_info.op == CIDR_MATCH_OP_ENDIF) { + DICT_CIDR_ENTRY *if_rule; + + if (nesting-- <= 0) + /* Already handled in dict_cidr_parse_rule(). */ + msg_panic("%s: ENDIF without IF", myname); + if_rule = rule_stack[nesting]; + if (if_rule->cidr_info.op != CIDR_MATCH_OP_IF) + msg_panic("%s: unexpected rule stack element type %d", + myname, if_rule->cidr_info.op); + if_rule->cidr_info.block_end = &(rule->cidr_info); + } if (last_rule == 0) dict_cidr->head = rule; else @@ -238,5 +317,12 @@ DICT *dict_cidr_open(const char *mapname, int open_flags, int dict_flags) last_rule = rule; } + while (nesting-- > 0) + msg_warn("cidr map %s, line %d: IF has no matching ENDIF", + mapname, rule_stack[nesting]->lineno); + + if (rule_stack) + (void) mvect_free(&mvect); + DICT_CIDR_OPEN_RETURN(DICT_DEBUG (&dict_cidr->dict)); } diff --git a/postfix/src/util/dict_cidr.in b/postfix/src/util/dict_cidr.in index 1798a60c9..ee20c1745 100644 --- a/postfix/src/util/dict_cidr.in +++ b/postfix/src/util/dict_cidr.in @@ -9,3 +9,15 @@ get 2001:240:5c7:0:2d0:b7ff:fe88:2ca7 get 2001:240:5c7:0:2d0:b7ff:febe:ca9f get 1.1.1.1 get 1:1:1:1:1:1:1:1 +get 1.2.3.3 +get 1.2.3.4 +get 1.2.3.5 +get 1.2.3.6 +get 1.2.3.7 +get 1.2.3.8 +get ::f3 +get ::f4 +get ::f5 +get ::f6 +get ::f7 +get ::f8 diff --git a/postfix/src/util/dict_cidr.map b/postfix/src/util/dict_cidr.map index 518e17149..b3b94f9b5 100644 --- a/postfix/src/util/dict_cidr.map +++ b/postfix/src/util/dict_cidr.map @@ -7,6 +7,32 @@ 172.999.0.0/21 whatever 172.16.1.999 whatever 172.16.1.4 +if 1.2.0.0/16 +if 1.2.3.4/30 +1.2.3.3 1.2.3.3 can't happen +1.2.3.4 1.2.3.4 can happen +1.2.3.5 1.2.3.5 can happen +1.2.3.6 1.2.3.6 can happen +1.2.3.7 1.2.3.7 can happen +1.2.3.8 1.2.3.8 can't happen +endif +endif +if !1.2.3.4/30 +1.2.3.3 1.2.3.3 can happen +1.2.3.8 1.2.3.8 can happen +endif +if ::f4/126 +::f3 ::f3 can't happen +::f4 ::f4 can happen +::f5 ::f5 can happen +::f6 ::f6 can happen +::f7 ::f7 can happen +::f8 ::f8 can't happen +endif +if !::f4/126 +::f3 ::f3 can happen +::f8 ::f8 can happen +endif 2001:240:5c7:0:2d0:b7ff:fe88:2ca7 match 2001:240:5c7:0:2d0:b7ff:fe88:2ca7 2001:240:5c7::/64 match netblock 2001:240:5c7::/64 1.0.0.0/0 match 0.0.0.0/0 @@ -16,3 +42,9 @@ [1234 foo [1234]junk bar 172.16.1.3/3x whatever +endif +endif +if 1:2::3:4 +if 1:2::3:5 +if ! +! diff --git a/postfix/src/util/dict_cidr.ref b/postfix/src/util/dict_cidr.ref index 4a1853928..ceb2c99cf 100644 --- a/postfix/src/util/dict_cidr.ref +++ b/postfix/src/util/dict_cidr.ref @@ -3,11 +3,17 @@ ./dict_open: warning: cidr map dict_cidr.map, line 7: bad net/mask pattern: "172.999.0.0/21": skipping this rule ./dict_open: warning: cidr map dict_cidr.map, line 8: bad address pattern: "172.16.1.999": skipping this rule ./dict_open: warning: cidr map dict_cidr.map, line 9: no lookup result: skipping this rule -./dict_open: warning: cidr map dict_cidr.map, line 12: non-null host address bits in "1.0.0.0/0", perhaps you should use "0.0.0.0/0" instead: skipping this rule -./dict_open: warning: cidr map dict_cidr.map, line 14: non-null host address bits in "1::/0", perhaps you should use "::/0" instead: skipping this rule -./dict_open: warning: cidr map dict_cidr.map, line 16: missing ']' character after "[1234": skipping this rule -./dict_open: warning: cidr map dict_cidr.map, line 17: garbage after "[1234]": skipping this rule -./dict_open: warning: cidr map dict_cidr.map, line 18: bad net/mask pattern: "172.16.1.3/3x": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 38: non-null host address bits in "1.0.0.0/0", perhaps you should use "0.0.0.0/0" instead: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 40: non-null host address bits in "1::/0", perhaps you should use "::/0" instead: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 42: missing ']' character after "[1234": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 43: garbage after "[1234]": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 44: bad net/mask pattern: "172.16.1.3/3x": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 45: ENDIF without IF: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 46: ENDIF without IF: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 49: no pattern: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 50: no pattern: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 48: IF has no matching ENDIF +./dict_open: warning: cidr map dict_cidr.map, line 47: IF has no matching ENDIF owner=untrusted (uid=USER) > get 172.16.0.0 172.16.0.0=554 match bad netblock 172.16.0.0/21 @@ -31,3 +37,27 @@ owner=untrusted (uid=USER) 1.1.1.1=match 0.0.0.0/0 > get 1:1:1:1:1:1:1:1 1:1:1:1:1:1:1:1=match ::/0 +> get 1.2.3.3 +1.2.3.3=1.2.3.3 can happen +> get 1.2.3.4 +1.2.3.4=1.2.3.4 can happen +> get 1.2.3.5 +1.2.3.5=1.2.3.5 can happen +> get 1.2.3.6 +1.2.3.6=1.2.3.6 can happen +> get 1.2.3.7 +1.2.3.7=1.2.3.7 can happen +> get 1.2.3.8 +1.2.3.8=1.2.3.8 can happen +> get ::f3 +::f3=::f3 can happen +> get ::f4 +::f4=::f4 can happen +> get ::f5 +::f5=::f5 can happen +> get ::f6 +::f6=::f6 can happen +> get ::f7 +::f7=::f7 can happen +> get ::f8 +::f8=::f8 can happen diff --git a/postfix/src/util/dict_pcre.c b/postfix/src/util/dict_pcre.c index 17c500d28..8f3032638 100644 --- a/postfix/src/util/dict_pcre.c +++ b/postfix/src/util/dict_pcre.c @@ -27,6 +27,11 @@ /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA /*--*/ #include "sys_defs.h" @@ -614,8 +619,8 @@ static DICT_PCRE_RULE *dict_pcre_parse_rule(const char *mapname, int lineno, while (*p && ISSPACE(*p)) ++p; if (!*p) - msg_warn("%s, line %d: no replacement text: using empty string", - mapname, lineno); + msg_warn("pcre map %s, line %d: no replacement text: " + "using empty string", mapname, lineno); /* * Sanity check the $number instances in the replacement text. @@ -882,6 +887,7 @@ DICT *dict_pcre_open(const char *mapname, int open_flags, int dict_flags) DICT_PCRE_IF_RULE *if_rule; if (nesting-- <= 0) + /* Already handled in dict_pcre_parse_rule(). */ msg_panic("%s: ENDIF without IF", myname); if (rule_stack[nesting]->op != DICT_PCRE_OP_IF) msg_panic("%s: unexpected rule stack element type %d", diff --git a/postfix/src/util/dict_pcre.in b/postfix/src/util/dict_pcre.in index 324342e83..b0644bdc6 100644 --- a/postfix/src/util/dict_pcre.in +++ b/postfix/src/util/dict_pcre.in @@ -6,6 +6,7 @@ get 3 get true3 get c get d +get 1235 get 1234 get 123 get bar/find diff --git a/postfix/src/util/dict_pcre.ref b/postfix/src/util/dict_pcre.ref index ca008ed86..23eef8e01 100644 --- a/postfix/src/util/dict_pcre.ref +++ b/postfix/src/util/dict_pcre.ref @@ -2,14 +2,14 @@ ./dict_open: warning: pcre map dict_pcre.map, line 1: do not prepend whitespace to statements between IF and ENDIF ./dict_open: warning: pcre map dict_pcre.map, line 5: ignoring extra text after ENDIF ./dict_open: warning: pcre map dict_pcre.map, line 8: unknown regexp option "!": skipping this rule -./dict_open: warning: dict_pcre.map, line 9: no replacement text: using empty string +./dict_open: warning: pcre map dict_pcre.map, line 9: no replacement text: using empty string ./dict_open: warning: pcre map dict_pcre.map, line 10: out of range replacement index "5": skipping this rule ./dict_open: warning: pcre map dict_pcre.map, line 17: $number found in negative match replacement text: skipping this rule ./dict_open: warning: pcre map dict_pcre.map, line 22: no regexp: skipping this rule -./dict_open: warning: pcre map dict_pcre.map, line 23: ignoring ENDIF without matching IF ./dict_open: warning: pcre map dict_pcre.map, line 24: ignoring ENDIF without matching IF +./dict_open: warning: pcre map dict_pcre.map, line 25: ignoring ENDIF without matching IF +./dict_open: warning: pcre map dict_pcre.map, line 27: IF has no matching ENDIF ./dict_open: warning: pcre map dict_pcre.map, line 26: IF has no matching ENDIF -./dict_open: warning: pcre map dict_pcre.map, line 25: IF has no matching ENDIF owner=untrusted (uid=USER) > get true true: not found @@ -27,6 +27,8 @@ true3=3 c= > get d d: not found +> get 1235 +1235=(1)(2)(3) > get 1234 1234=(1)(2)(3)(4) > get 123 diff --git a/postfix/src/util/dict_regexp.c b/postfix/src/util/dict_regexp.c index 953756bf2..1e5c449d3 100644 --- a/postfix/src/util/dict_regexp.c +++ b/postfix/src/util/dict_regexp.c @@ -31,6 +31,11 @@ /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA /*--*/ /* System library. */ @@ -575,8 +580,8 @@ static DICT_REGEXP_RULE *dict_regexp_parseline(const char *mapname, int lineno, while (*p && ISSPACE(*p)) ++p; if (!*p) { - msg_warn("regexp map %s, line %d: using empty replacement string", - mapname, lineno); + msg_warn("regexp map %s, line %d: no replacement text: " + "using empty string", mapname, lineno); } /* @@ -819,6 +824,7 @@ DICT *dict_regexp_open(const char *mapname, int open_flags, int dict_flags) DICT_REGEXP_IF_RULE *if_rule; if (nesting-- <= 0) + /* Already handled in dict_regexp_parseline(). */ msg_panic("%s: ENDIF without IF", myname); if (rule_stack[nesting]->op != DICT_REGEXP_OP_IF) msg_panic("%s: unexpected rule stack element type %d", diff --git a/postfix/src/util/dict_regexp.ref b/postfix/src/util/dict_regexp.ref index 7b5bc88a9..2a08a3b73 100644 --- a/postfix/src/util/dict_regexp.ref +++ b/postfix/src/util/dict_regexp.ref @@ -1,7 +1,7 @@ ./dict_open: warning: regexp map dict_regexp.map, line 1: ignoring extra text after IF statement: "fodder" ./dict_open: warning: regexp map dict_regexp.map, line 1: do not prepend whitespace to statements between IF and ENDIF ./dict_open: warning: regexp map dict_regexp.map, line 5: ignoring extra text after ENDIF -./dict_open: warning: regexp map dict_regexp.map, line 9: using empty replacement string +./dict_open: warning: regexp map dict_regexp.map, line 9: no replacement text: using empty string ./dict_open: warning: regexp map dict_regexp.map, line 10: out of range replacement index "5": skipping this rule ./dict_open: warning: regexp map dict_regexp.map, line 17: $number found in negative match replacement text: skipping this rule ./dict_open: warning: regexp map dict_regexp.map, line 22: no regexp: skipping this rule diff --git a/postfix/src/util/mvect.c b/postfix/src/util/mvect.c index 3353799f3..cf4b0d5bb 100644 --- a/postfix/src/util/mvect.c +++ b/postfix/src/util/mvect.c @@ -54,6 +54,11 @@ /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA /*--*/ /* System library. */