Added the long-awaited ${if match_ip condition.
authorPhilip Hazel <ph10@hermes.cam.ac.uk>
Wed, 22 Jun 2005 10:17:22 +0000 (10:17 +0000)
committerPhilip Hazel <ph10@hermes.cam.ac.uk>
Wed, 22 Jun 2005 10:17:22 +0000 (10:17 +0000)
doc/doc-misc/WishList
doc/doc-txt/ChangeLog
doc/doc-txt/NewStuff
src/src/expand.c
src/src/functions.h
src/src/match.c
src/src/verify.c

index 7624940..c31891b 100644 (file)
@@ -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".
index 158929d..8a9179f 100644 (file)
@@ -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
 -----------------
index 0805e0b..7ac5395 100644 (file)
@@ -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
 ------------
index 5432b98..80971cb 100644 (file)
@@ -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);
index 2c8c432..00d0f67 100644 (file)
@@ -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);
index 106cb6a..18f1a37 100644 (file)
@@ -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:
index 2936e7c..cdf5bb7 100644 (file)
@@ -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;<file|query> 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;<file|query> 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 != '_' &&