Merge branch 'master' of ssh://git.exim.org/home/git/exim
[exim.git] / src / src / match.c
index 9d2a1c0bd892b120557f9c582332b3c9018dd0d3..66ae3dddbbc02387867f8de0f90052a6c30d43d4 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/match.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2009 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for matching strings */
@@ -74,7 +72,8 @@ Arguments:
                    returns ERROR)
 
 Contents of the argument block:
-  subject        the subject string to be checked
+  origsubject    the subject in its original casing
+  subject        the subject string to be checked, lowercased if caseless
   expand_setup   if < 0, don't set up any numeric expansion variables;
                  if = 0, set $0 to whole subject, and either
                    $1 to what matches * or
@@ -99,7 +98,7 @@ check_string_block *cb = (check_string_block *)arg;
 int search_type, partial, affixlen, starflags;
 int expand_setup = cb->expand_setup;
 uschar *affix;
-uschar *s = cb->subject;
+uschar *s;
 uschar *filename = NULL;
 uschar *keyquery, *result, *semicolon;
 void *handle;
@@ -108,6 +107,12 @@ error = error;  /* Keep clever compilers from complaining */
 
 if (valueptr != NULL) *valueptr = NULL;  /* For non-lookup matches */
 
+/* For regular expressions, use cb->origsubject rather than cb->subject so that
+it works if the pattern uses (?-i) to turn off case-independence, overriding
+"caseless". */
+
+s = (pattern[0] == '^')? cb->origsubject : cb->subject;
+
 /* If required to set up $0, initialize the data but don't turn on by setting
 expand_nmax until the match is assured. */
 
@@ -174,7 +179,9 @@ if (cb->at_is_special && pattern[0] == '@')
     int slen = Ustrlen(s);
     if (s[0] != '[' && s[slen-1] != ']') return FAIL;
     for (ip = host_find_interfaces(); ip != NULL; ip = ip->next)
-      if (Ustrncmp(ip->address, s+1, slen - 2) == 0) return OK;
+      if (Ustrncmp(ip->address, s+1, slen - 2) == 0
+            && ip->address[slen - 2] == 0)
+        return OK;
     return FAIL;
     }
 
@@ -266,12 +273,20 @@ up user@domain for sender rejection). There's a flag to disable it. */
 
 if (!cb->use_partial) partial = -1;
 
-/* Set the parameters for the two different kinds of lookup. */
+/* Set the parameters for the three different kinds of lookup. */
 
 keyquery = semicolon + 1;
 while (isspace(*keyquery)) keyquery++;
 
-if (!mac_islookup(search_type, lookup_querystyle))
+if (mac_islookup(search_type, lookup_absfilequery))
+  {
+  filename = keyquery;
+  while (*keyquery != 0 && !isspace(*keyquery)) keyquery++;
+  filename = string_copyn(filename, keyquery - filename);
+  while (isspace(*keyquery)) keyquery++;
+  }
+
+else if (!mac_islookup(search_type, lookup_querystyle))
   {
   filename = keyquery;
   keyquery = s;
@@ -415,7 +430,7 @@ Returns:       OK    if matched a non-negated item
                FAIL  if expansion force-failed
                FAIL  if matched a negated item
                FAIL  if hit end of list after a non-negated item
-               DEFER if a lookup deferred or expansion failed
+               DEFER if a something deferred or expansion failed
 */
 
 int
@@ -427,6 +442,8 @@ int yield = OK;
 unsigned int *original_cache_bits = *cache_ptr;
 BOOL include_unknown = FALSE;
 BOOL ignore_unknown = FALSE;
+BOOL include_defer = FALSE;
+BOOL ignore_defer = FALSE;
 uschar *list;
 uschar *sss;
 uschar *ot = NULL;
@@ -464,7 +481,19 @@ if (type >= MCL_NOEXPAND)
   }
 else
   {
-  list = expand_string(*listptr);
+  /* If we are searching a domain list, and $domain is not set, set it to the
+  subject that is being sought for the duration of the expansion. */
+
+  if (type == MCL_DOMAIN && deliver_domain == NULL)
+    {
+    check_string_block *cb = (check_string_block *)arg;
+    deliver_domain = cb->subject;
+    list = expand_string(*listptr);
+    deliver_domain = NULL;
+    }
+
+  else list = expand_string(*listptr);
+
   if (list == NULL)
     {
     if (expand_string_forcedfail)
@@ -524,10 +553,11 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
       }
     }
 
-  /* If the host item is "+include_unknown", remember it in case there's a
-  subsequent failed reverse lookup. */
+  /* If the host item is "+include_unknown" or "+ignore_unknown", remember it
+  in case there's a subsequent failed reverse lookup. There is similar
+  processing for "defer". */
 
-  else if (type == MCL_HOST)
+  else if (type == MCL_HOST && *ss == '+')
     {
     if (Ustrcmp(ss, "+include_unknown") == 0)
       {
@@ -541,6 +571,18 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
       include_unknown = FALSE;
       continue;
       }
+    if (Ustrcmp(ss, "+include_defer") == 0)
+      {
+      include_defer = TRUE;
+      ignore_defer = FALSE;
+      continue;
+      }
+    if (Ustrcmp(ss, "+ignore_defer") == 0)
+      {
+      ignore_defer = TRUE;
+      include_defer = FALSE;
+      continue;
+      }
     }
 
   /* Starting with ! specifies a negative item. It is theoretically possible
@@ -630,9 +672,11 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
             store_pool = POOL_PERM;
             p = store_get(sizeof(namedlist_cacheblock));
             p->key = string_copy(get_check_key(arg, type));
+
+
+            p->data = (*valueptr == NULL)? NULL : string_copy(*valueptr);
             store_pool = old_pool;
 
-            p->data = *valueptr;
             p->next = nb->cache_data;
             nb->cache_data = p;
             if (*valueptr != NULL)
@@ -684,7 +728,7 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
 
     else
       {
-      uschar *error;
+      uschar *error = NULL;
       switch ((func)(arg, ss, valueptr, &error))
         {
         case OK:
@@ -693,23 +737,42 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
         return yield;
 
         case DEFER:
+        if (error == NULL)
+          error = string_sprintf("DNS lookup of %s deferred", ss);
+        if (ignore_defer)
+          {
+          HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_defer\n",
+            error);
+          break;
+          }
+        if (include_defer)
+          {
+          log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
+          return OK;
+          }
         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:
         if (ignore_unknown)
           {
-          HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown",
+          HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown\n",
             error);
           }
         else
           {
           HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot,
             include_unknown? "yes":"no", error);
-          if (!include_unknown) return FAIL;
+          if (!include_unknown)
+            {
+            if ((log_extra_selector & LX_unknown_in_list) != 0)
+              log_write(0, LOG_MAIN, "list matching forced to fail: %s", error);
+            return FAIL;
+            }
           log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
           return OK;
           }
@@ -780,27 +843,45 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
       switch ((func)(arg, ss, valueptr, &error))
         {
         case OK:
-        fclose(f);
+        (void)fclose(f);
         HDEBUG(D_lists) debug_printf("%s %s (matched \"%s\" in %s)\n", ot,
           (yield == OK)? "yes" : "no", sss, filename);
         return file_yield;
 
         case DEFER:
-        fclose(f);
+        if (error == NULL)
+          error = string_sprintf("DNS lookup of %s deferred", ss);
+        if (ignore_defer)
+          {
+          HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_defer\n",
+            error);
+          break;
+          }
+        (void)fclose(f);
+        if (include_defer)
+          {
+          log_write(0, LOG_MAIN, "%s: accepted by +include_defer", error);
+          return OK;
+          }
         goto DEFER_RETURN;
 
         case ERROR:          /* host name lookup failed - this can only */
         if (ignore_unknown)  /* be for an incoming host (not outgoing) */
           {
-          HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown",
+          HDEBUG(D_lists) debug_printf("%s: item ignored by +ignore_unknown\n",
             error);
           }
         else
          {
           HDEBUG(D_lists) debug_printf("%s %s (%s)\n", ot,
             include_unknown? "yes":"no", error);
-          fclose(f);
-          if (!include_unknown) return FAIL;
+          (void)fclose(f);
+          if (!include_unknown)
+            {
+            if ((log_extra_selector & LX_unknown_in_list) != 0)
+              log_write(0, LOG_MAIN, "list matching forced to fail: %s", error);
+            return FAIL;
+            }
           log_write(0, LOG_MAIN, "%s: accepted by +include_unknown", error);
           return OK;
           }
@@ -811,7 +892,7 @@ while ((sss = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
     for the file, in case this is the last item in the list. */
 
     yield = file_yield;
-    fclose(f);
+    (void)fclose(f);
     }
   }    /* Loop for the next item on the top-level list */
 
@@ -821,10 +902,10 @@ HDEBUG(D_lists)
   debug_printf("%s %s (end of list)\n", ot, (yield == OK)? "no":"yes");
 return (yield == OK)? FAIL : OK;
 
-/* Handle lookup defer */
+/* Something deferred */
 
 DEFER_RETURN:
-HDEBUG(D_lists) debug_printf("%s lookup deferred for %s\n", ot, sss);
+HDEBUG(D_lists) debug_printf("%s list match deferred for %s\n", ot, sss);
 return DEFER;
 }
 
@@ -929,35 +1010,41 @@ uschar *s, *pdomain, *sdomain;
 
 error = error;  /* Keep clever compilers from complaining */
 
-DEBUG(D_lists) debug_printf("address match: subject=%s pattern=%s\n",
+DEBUG(D_lists) debug_printf("address match test: subject=%s pattern=%s\n",
   subject, pattern);
 
-/* Handle a regular expression, which must match the entire incoming address. */
+/* Find the subject's domain */
 
-if (*pattern == '^')
-  return match_check_string(subject, pattern, cb->expand_setup, TRUE,
-    cb->caseless, FALSE, NULL);
+sdomain = Ustrrchr(subject, '@');
 
-/* If the subject is the empty string, the only pattern it can match (other
-than a regular expression) is the empty pattern. */
+/* The only case where a subject may not have a domain is if the subject is
+empty. Otherwise, a subject with no domain is a serious configuration error. */
 
-if (*subject == 0) return (*pattern == 0)? OK : FAIL;
+if (sdomain == NULL && *subject != 0)
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "no @ found in the subject of an "
+    "address list match: subject=\"%s\" pattern=\"%s\"", subject, pattern);
+  return FAIL;
+  }
 
-/* Find the domain in the subject */
+/* Handle a regular expression, which must match the entire incoming address.
+This may be the empty address. */
 
-sdomain = Ustrrchr(subject, '@');
+if (*pattern == '^')
+  return match_check_string(subject, pattern, cb->expand_setup, TRUE,
+    cb->caseless, FALSE, NULL);
 
-/* Handle the case of a pattern that is just a lookup. Skip over possible
-lookup names (letters, digits, hyphens). Skip over a possible * or *@ at
-the end. Then we must have a semicolon for it to be a lookup. */
+/* Handle a pattern that is just a lookup. Skip over possible lookup names
+(letters, digits, hyphens). Skip over a possible * or *@ at the end. Then we
+must have a semicolon for it to be a lookup. */
 
 for (s = pattern; isalnum(*s) || *s == '-'; s++);
 if (*s == '*') s++;
 if (*s == '@') s++;
 
-/* If it is a straight lookup, do a lookup for the whole address. Partial
-matching doesn't make sense here, so we ignore it, but write a panic log entry.
-However, *@ matching will be honoured. */
+/* If it is a straight lookup, do a lookup for the whole address. This may be
+the empty address. Partial matching doesn't make sense here, so we ignore it,
+but write a panic log entry. However, *@ matching will be honoured. */
 
 if (*s == ';')
   {
@@ -968,6 +1055,12 @@ if (*s == ';')
     valueptr);
   }
 
+/* For the remaining cases, an empty subject matches only an empty pattern,
+because other patterns expect to have a local part and a domain to match
+against. */
+
+if (*subject == 0) return (*pattern == 0)? OK : FAIL;
+
 /* If the pattern starts with "@@" we have a split lookup, where the domain is
 looked up to obtain a list of local parts. If the subject's local part is just
 "*" (called from retry) the match always fails. */