From 32d668a555bb486a27f1899ef8156321ef27816a Mon Sep 17 00:00:00 2001 From: Philip Hazel Date: Wed, 22 Jun 2005 10:17:22 +0000 Subject: [PATCH] Added the long-awaited ${if match_ip condition. --- doc/doc-misc/WishList | 20 +----- doc/doc-txt/ChangeLog | 4 +- doc/doc-txt/NewStuff | 37 +++++++++- src/src/expand.c | 41 ++++++++++- src/src/functions.h | 3 +- src/src/match.c | 7 +- src/src/verify.c | 161 ++++++++++++++++++++++++++---------------- 7 files changed, 185 insertions(+), 88 deletions(-) diff --git a/doc/doc-misc/WishList b/doc/doc-misc/WishList index 7624940c4..c31891b46 100644 --- a/doc/doc-misc/WishList +++ b/doc/doc-misc/WishList @@ -1,4 +1,4 @@ -$Cambridge: exim/doc/doc-misc/WishList,v 1.40 2005/06/21 14:14:55 ph10 Exp $ +$Cambridge: exim/doc/doc-misc/WishList,v 1.41 2005/06/22 10:17:22 ph10 Exp $ EXIM 4 WISH LIST ---------------- @@ -864,24 +864,6 @@ So as to avoid duplication problems when sending multiple addresses in multiple copies to the same address. ------------------------------------------------------------------------------ -(73) 17-Jul-02 M Match a list from within a condition - -e.g. ${if matchdomain {$domain}{+domainlist} ... - ${if matchhost {$sender_host_address}{1.2.3.4/10:2.3.4.5/16}... - -Thought needed about how to handle host names. This may be too messy to specify -cleanly. - -22-Apr-04: Implemented for domains, addresses, and local parts. Hosts are -too messy! - -The only sensible approach seems to be to allow IP address arguments only. -Anything else should be diagnosed as an error. However, if a name appears in -the list, a PTR lookup should be done. This may require a lot of refactoring -in the code, because of the current assumption that were are (almost) always -dealing with THE sending host. ------------------------------------------------------------------------------- - (74) 22-Jul-02 M Extend -bV to do more semantic checking For example, diagnose "local_hosts" that should probably be "+local_hosts". diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 158929d03..8a9179f44 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -1,4 +1,4 @@ -$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.166 2005/06/21 14:14:55 ph10 Exp $ +$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.167 2005/06/22 10:17:22 ph10 Exp $ Change log file for Exim from version 4.21 ------------------------------------------- @@ -174,6 +174,8 @@ PH/22 Fixed some oversights/typos causing bugs when Exim is compiled with PH/23 Added daemon_startup_retries and daemon_startup_sleep. +PH/24 Added ${if match_ip condition. + Exim version 4.51 ----------------- diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index 0805e0b5b..7ac53952d 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -1,4 +1,4 @@ -$Cambridge: exim/doc/doc-txt/NewStuff,v 1.51 2005/06/21 14:14:55 ph10 Exp $ +$Cambridge: exim/doc/doc-txt/NewStuff,v 1.52 2005/06/22 10:17:22 ph10 Exp $ New Features in Exim -------------------- @@ -349,6 +349,41 @@ PH/04 There are two new options that control the retrying done by the daemon of retries after the first failure (default 9); daemon_startup_sleep defines the length of time to wait between retries (default 30s). +PH/05 There is now a new ${if condition called "match_ip". It is similar to + match_domain, etc. It must be followed by two argument strings. The first + (after expansion) must be an IP address or an empty string. The second + (after expansion) is a restricted host list that can match only an IP + address, not a host name. For example: + + ${if match_ip{$sender_host_address}{1.2.3.4:5.6.7.8}{...}{...}} + + The specific types of host list item that are permitted in the list are + shown below. Consult the manual section on host lists for further + details. + + . An IP address, optionally with a CIDR mask. + + . A single asterisk matches any IP address. + + . An empty item matches only if the IP address is empty. This could be + useful for testing for a locally submitted message or one from specific + hosts in a single test such as + + ${if match_ip{$sender_host_address}{:4.3.2.1:...}{...}{...}} + + where the first item in the list is the empty string. + + . The item @[] matches any of the local host's interface addresses. + + . Lookups are assumed to be "net-" style lookups, even if "net-" is not + specified. Thus, the following are equivalent: + + ${if match_ip{$sender_host_address}{lsearch;/some/file}... + ${if match_ip{$sender_host_address}{net-lsearch;/some/file}... + + You do need to specify the "net-" prefix if you want to specify a + specific address mask, for example, by using "net24-". + Version 4.51 ------------ diff --git a/src/src/expand.c b/src/src/expand.c index 5432b9812..80971cb26 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/expand.c,v 1.33 2005/06/20 13:58:22 ph10 Exp $ */ +/* $Cambridge: exim/src/src/expand.c,v 1.34 2005/06/22 10:17:23 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -195,6 +195,7 @@ static uschar *cond_table[] = { US"match", US"match_address", US"match_domain", + US"match_ip", US"match_local_part", US"or", US"pam", @@ -233,6 +234,7 @@ enum { ECOND_MATCH, ECOND_MATCH_ADDRESS, ECOND_MATCH_DOMAIN, + ECOND_MATCH_IP, ECOND_MATCH_LOCAL_PART, ECOND_OR, ECOND_PAM, @@ -1801,6 +1803,7 @@ switch(cond_type) variables if it succeeds match_address: matches in an address list match_domain: matches in a domain list + match_ip: matches a host list that is restricted to IP addresses match_local_part: matches in a local part list crypteq: encrypts plaintext and compares against an encrypted text, using crypt(), crypt16(), MD5 or SHA-1 @@ -1809,6 +1812,7 @@ switch(cond_type) case ECOND_MATCH: case ECOND_MATCH_ADDRESS: case ECOND_MATCH_DOMAIN: + case ECOND_MATCH_IP: case ECOND_MATCH_LOCAL_PART: case ECOND_CRYPTEQ: @@ -1962,6 +1966,41 @@ switch(cond_type) MCL_DOMAIN + MCL_NOEXPAND, TRUE, NULL); goto MATCHED_SOMETHING; + case ECOND_MATCH_IP: /* Match IP address in a host list */ + if (sub[0][0] != 0 && string_is_ip_address(sub[0], NULL) <= 0) + { + expand_string_message = string_sprintf("\"%s\" is not an IP address", + sub[0]); + return NULL; + } + else + { + unsigned int *nullcache = NULL; + check_host_block cb; + + cb.host_name = US""; + cb.host_address = sub[0]; + + /* If the host address starts off ::ffff: it is an IPv6 address in + IPv4-compatible mode. Find the IPv4 part for checking against IPv4 + addresses. */ + + cb.host_ipv4 = (Ustrncmp(cb.host_address, "::ffff:", 7) == 0)? + cb.host_address + 7 : cb.host_address; + + rc = match_check_list( + &sub[1], /* the list */ + 0, /* separator character */ + &hostlist_anchor, /* anchor pointer */ + &nullcache, /* cache pointer */ + check_host, /* function for testing */ + &cb, /* argument for function */ + MCL_HOST, /* type of check */ + sub[0], /* text for debugging */ + NULL); /* where to pass back data */ + } + goto MATCHED_SOMETHING; + case ECOND_MATCH_LOCAL_PART: rc = match_isinlist(sub[0], &(sub[1]), 0, &localpartlist_anchor, NULL, MCL_LOCALPART + MCL_NOEXPAND, TRUE, NULL); diff --git a/src/src/functions.h b/src/src/functions.h index 2c8c4321d..00d0f6768 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/functions.h,v 1.16 2005/06/20 10:04:55 ph10 Exp $ */ +/* $Cambridge: exim/src/src/functions.h,v 1.17 2005/06/22 10:17:23 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -52,6 +52,7 @@ extern int auth_get_no64_data(uschar **, uschar *); extern uschar *auth_xtextencode(uschar *, int); extern int auth_xtextdecode(uschar *, uschar **); +extern int check_host(void *, uschar *, uschar **, uschar **); extern uschar **child_exec_exim(int, BOOL, int *, BOOL, int, ...); extern pid_t child_open_uid(uschar **, uschar **, int, uid_t *, gid_t *, int *, int *, uschar *, BOOL); diff --git a/src/src/match.c b/src/src/match.c index 106cb6a35..18f1a3791 100644 --- a/src/src/match.c +++ b/src/src/match.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/match.c,v 1.5 2005/02/17 11:58:26 ph10 Exp $ */ +/* $Cambridge: exim/src/src/match.c,v 1.6 2005/06/22 10:17:23 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -697,8 +697,9 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL) case DEFER: goto DEFER_RETURN; - /* The ERROR return occurs only when checking hosts, when either a - forward or reverse lookup has failed. The error string gives details of + /* The ERROR return occurs when checking hosts, when either a forward + or reverse lookup has failed. It can also occur in a match_ip list if a + non-IP address item is encountered. The error string gives details of which it was. */ case ERROR: diff --git a/src/src/verify.c b/src/src/verify.c index 2936e7cbd..cdf5bb78d 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/verify.c,v 1.19 2005/06/17 10:20:30 ph10 Exp $ */ +/* $Cambridge: exim/src/src/verify.c,v 1.20 2005/06/22 10:17:23 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -1816,25 +1816,34 @@ Arguments: error for error message when returning ERROR The block contains: - host_name the host name or NULL, implying use sender_host_name and - sender_host_aliases, looking them up if required + host_name (a) the host name, or + (b) NULL, implying use sender_host_name and + sender_host_aliases, looking them up if required, or + (c) the empty string, meaning that only IP address matches + are permitted host_address the host address host_ipv4 the IPv4 address taken from an IPv6 one Returns: OK matched FAIL did not match DEFER lookup deferred - ERROR failed to find the host name or IP address - unknown lookup type specified + ERROR (a) failed to find the host name or IP address, or + (b) unknown lookup type specified, or + (c) host name encountered when only IP addresses are + being matched */ -static int +int check_host(void *arg, uschar *ss, uschar **valueptr, uschar **error) { check_host_block *cb = (check_host_block *)arg; +int mlen = -1; int maskoffset; +BOOL iplookup = FALSE; BOOL isquery = FALSE; -uschar *semicolon, *t; +BOOL isiponly = cb->host_name != NULL && cb->host_name[0] == 0; +uschar *t = ss; +uschar *semicolon; uschar **aliases; /* Optimize for the special case when the pattern is "*". */ @@ -1848,12 +1857,17 @@ situation, the host address is the empty string. */ if (cb->host_address[0] == 0) return (*ss == 0)? OK : FAIL; if (*ss == 0) return FAIL; -/* If the pattern is precisely "@" then match against the primary host name; -if it's "@[]" match against the local host's IP addresses. */ +/* If the pattern is precisely "@" then match against the primary host name, +provided that host name matching is permitted; if it's "@[]" match against the +local host's IP addresses. */ if (*ss == '@') { - if (ss[1] == 0) ss = primary_hostname; + if (ss[1] == 0) + { + if (isiponly) return ERROR; + ss = primary_hostname; + } else if (Ustrcmp(ss, "@[]") == 0) { ip_address_item *ip; @@ -1869,73 +1883,96 @@ a (possibly masked) comparision with the current IP address. */ if (string_is_ip_address(ss, &maskoffset) > 0) return (host_is_in_net(cb->host_address, ss, maskoffset)? OK : FAIL); -/* If the item is of the form net[n]-lookup; then it is a lookup on -a masked IP network, in textual form. The net- stuff really only applies to -single-key lookups where the key is implicit. For query-style lookups the key -is specified in the query. From release 4.30, the use of net- for query style -is no longer needed, but we retain it for backward compatibility. */ +/* See if there is a semicolon in the pattern */ -if (Ustrncmp(ss, "net", 3) == 0 && (semicolon = Ustrchr(ss, ';')) != NULL) +semicolon = Ustrchr(ss, ';'); + +/* If we are doing an IP address only match, then all lookups must be IP +address lookups. */ + +if (isiponly) { - int mlen = 0; - for (t = ss + 3; isdigit(*t); t++) mlen = mlen * 10 + *t - '0'; - if (*t++ == '-') - { - int insize; - int search_type; - int incoming[4]; - void *handle; - uschar *filename, *key, *result; - uschar buffer[64]; + iplookup = semicolon != NULL; + } - /* If no mask was supplied, set a negative value */ +/* Otherwise, if the item is of the form net[n]-lookup; then it is +a lookup on a masked IP network, in textual form. The net- stuff really only +applies to single-key lookups where the key is implicit. For query-style +lookups the key is specified in the query. From release 4.30, the use of net- +for query style is no longer needed, but we retain it for backward +compatibility. */ - if (mlen == 0 && t == ss+4) mlen = -1; +else if (Ustrncmp(ss, "net", 3) == 0 && semicolon != NULL) + { + mlen = 0; + for (t = ss + 3; isdigit(*t); t++) mlen = mlen * 10 + *t - '0'; + if (mlen == 0 && t == ss+3) mlen = -1; /* No mask supplied */ + iplookup = (*t++ == '-'); + } - /* Find the search type */ +/* Do the IP address lookup if that is indeed what we have */ - search_type = search_findtype(t, semicolon - t); +if (iplookup) + { + int insize; + int search_type; + int incoming[4]; + void *handle; + uschar *filename, *key, *result; + uschar buffer[64]; - if (search_type < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", - search_error_message); + /* Find the search type */ - /* Adjust parameters for the type of lookup. For a query-style - lookup, there is no file name, and the "key" is just the query. For - a single-key lookup, the key is the current IP address, masked - appropriately, and reconverted to text form, with the mask appended. - For IPv6 addresses, specify dot separators instead of colons. */ + search_type = search_findtype(t, semicolon - t); - if (mac_islookup(search_type, lookup_querystyle)) - { - filename = NULL; - key = semicolon + 1; - } - else - { - insize = host_aton(cb->host_address, incoming); - host_mask(insize, incoming, mlen); - (void)host_nmtoa(insize, incoming, mlen, buffer, '.'); - key = buffer; - filename = semicolon + 1; - } + if (search_type < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", + search_error_message); - /* Now do the actual lookup; note that there is no search_close() because - of the caching arrangements. */ + /* Adjust parameters for the type of lookup. For a query-style + lookup, there is no file name, and the "key" is just the query. For + a single-key lookup, the key is the current IP address, masked + appropriately, and reconverted to text form, with the mask appended. + For IPv6 addresses, specify dot separators instead of colons. */ - handle = search_open(filename, search_type, 0, NULL, NULL); - if (handle == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", - search_error_message); - result = search_find(handle, filename, key, -1, NULL, 0, 0, NULL); - if (valueptr != NULL) *valueptr = result; - return (result != NULL)? OK : search_find_defer? DEFER: FAIL; + if (mac_islookup(search_type, lookup_querystyle)) + { + filename = NULL; + key = semicolon + 1; + } + else + { + insize = host_aton(cb->host_address, incoming); + host_mask(insize, incoming, mlen); + (void)host_nmtoa(insize, incoming, mlen, buffer, '.'); + key = buffer; + filename = semicolon + 1; } + + /* Now do the actual lookup; note that there is no search_close() because + of the caching arrangements. */ + + handle = search_open(filename, search_type, 0, NULL, NULL); + if (handle == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", + search_error_message); + result = search_find(handle, filename, key, -1, NULL, 0, 0, NULL); + if (valueptr != NULL) *valueptr = result; + return (result != NULL)? OK : search_find_defer? DEFER: FAIL; } /* The pattern is not an IP address or network reference of any kind. That is, -it is a host name pattern. Check the characters of the pattern to see if they -comprise only letters, digits, full stops, and hyphens (the constituents of -domain names). Allow underscores, as they are all too commonly found. Sigh. -Also, if allow_utf8_domains is set, allow top-bit characters. */ +it is a host name pattern. If this is an IP only match, there's an error in the +host list. */ + +if (isiponly) + { + *error = US"cannot match host name in match_ip list"; + return ERROR; + } + +/* Check the characters of the pattern to see if they comprise only letters, +digits, full stops, and hyphens (the constituents of domain names). Allow +underscores, as they are all too commonly found. Sigh. Also, if +allow_utf8_domains is set, allow top-bit characters. */ for (t = ss; *t != 0; t++) if (!isalnum(*t) && *t != '.' && *t != '-' && *t != '_' && -- 2.25.1