Update version number and copyright year.
[exim.git] / src / src / verify.c
index d180fdaa90423e6a14d9b61e30041dbecfc3a844..141446aa7d5a7bbc288bd286d0aca7047a357ba1 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/verify.c,v 1.40 2006/10/03 10:25:55 ph10 Exp $ */
+/* $Cambridge: exim/src/src/verify.c,v 1.45 2007/01/08 10:50:18 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2006 */
+/* Copyright (c) University of Cambridge 1995 - 2007 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with verifying things. The original code for callout
@@ -441,9 +441,10 @@ for (host = host_list; host != NULL && !done; host = host->next)
   if (tf->helo_data != NULL)
     {
     uschar *s = expand_string(tf->helo_data);
-    if (active_hostname == NULL)
+    if (s == NULL)
       log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: failed to expand transport's "
-        "helo_data value for callout: %s", expand_string_message);
+        "helo_data value for callout: %s", addr->address,
+        expand_string_message);
     else active_hostname = s;
     }
 
@@ -1009,17 +1010,11 @@ information about the top level address, not anything that it generated. */
 while (addr_new != NULL)
   {
   int rc;
-  uschar *show_address;
   address_item *addr = addr_new;
 
   addr_new = addr->next;
   addr->next = NULL;
 
-  /* When full_info is set, child addresses are displayed in top-level
-  messages. Otherwise, we show only the top level address. */
-
-  show_address = full_info? addr->address : address;
-
   DEBUG(D_verify)
     {
     debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
@@ -1141,6 +1136,7 @@ while (addr_new != NULL)
             }
           else
             {
+            int flags;
             uschar *canonical_name;
             host_item *host, *nexthost;
             host_build_hostlist(&host_list, s, tf.hosts_randomize);
@@ -1151,20 +1147,19 @@ while (addr_new != NULL)
             additional host items being inserted into the chain. Hence we must
             save the next host first. */
 
+            flags = HOST_FIND_BY_A;
+            if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
+            if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
+
             for (host = host_list; host != NULL; host = nexthost)
               {
               nexthost = host->next;
               if (tf.gethostbyname ||
                   string_is_ip_address(host->name, NULL) != 0)
-                (void)host_find_byname(host, NULL, &canonical_name, TRUE);
+                (void)host_find_byname(host, NULL, flags, &canonical_name, TRUE);
               else
-                {
-                int flags = HOST_FIND_BY_A;
-                if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
-                if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
                 (void)host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
                   &canonical_name, NULL);
-                }
               }
             }
           }
@@ -1215,7 +1210,7 @@ while (addr_new != NULL)
       {
       address_item *p = addr->parent;
 
-      fprintf(f, "%s%s %s", ko_prefix, show_address,
+      fprintf(f, "%s%s %s", ko_prefix, full_info? addr->address : address,
         address_test_mode? "is undeliverable" : "failed to verify");
       if (!expn && admin_user)
         {
@@ -1248,7 +1243,7 @@ while (addr_new != NULL)
       {
       address_item *p = addr->parent;
       fprintf(f, "%s%s cannot be resolved at this time", ko_prefix,
-        show_address);
+        full_info? addr->address : address);
       if (!expn && admin_user)
         {
         if (addr->basic_errno > 0)
@@ -1321,7 +1316,7 @@ while (addr_new != NULL)
          (addr_new != NULL &&            /* At least one new address AND */
           success_on_redirect)))         /* success_on_redirect is set */
       {
-      if (f != NULL) fprintf(f, "%s %s\n", show_address,
+      if (f != NULL) fprintf(f, "%s %s\n", address,
         address_test_mode? "is deliverable" : "verified");
 
       /* If we have carried on to verify a child address, we want the value
@@ -1457,8 +1452,9 @@ verify_check_headers(uschar **msgptr)
 {
 header_line *h;
 uschar *colon, *s;
+int yield = OK;
 
-for (h = header_list; h != NULL; h = h->next)
+for (h = header_list; h != NULL && yield == OK; h = h->next)
   {
   if (h->type != htype_from &&
       h->type != htype_reply_to &&
@@ -1472,9 +1468,10 @@ for (h = header_list; h != NULL; h = h->next)
   s = colon + 1;
   while (isspace(*s)) s++;
 
-  parse_allow_group = TRUE;     /* Allow group syntax */
+  /* Loop for multiple addresses in the header, enabling group syntax. Note
+  that we have to reset this after the header has been scanned. */
 
-  /* Loop for multiple addresses in the header */
+  parse_allow_group = TRUE;
 
   while (*s != 0)
     {
@@ -1484,7 +1481,7 @@ for (h = header_list; h != NULL; h = h->next)
     int start, end, domain;
 
     /* Temporarily terminate the string at this point, and extract the
-    operative address within. */
+    operative address within, allowing group syntax. */
 
     *ss = 0;
     recipient = parse_extract_address(s,&errmess,&start,&end,&domain,FALSE);
@@ -1540,7 +1537,8 @@ for (h = header_list; h != NULL; h = h->next)
         string_sprintf("%s: failing address in \"%.*s:\" header %s: %.*s",
           errmess, tt - h->text, h->text, verb, len, s));
 
-      return FAIL;
+      yield = FAIL;
+      break;          /* Out of address loop */
       }
 
     /* Advance to the next address */
@@ -1548,9 +1546,12 @@ for (h = header_list; h != NULL; h = h->next)
     s = ss + (terminator? 1:0);
     while (isspace(*s)) s++;
     }   /* Next address */
-  }     /* Next header */
 
-return OK;
+  parse_allow_group = FALSE;
+  parse_found_group = FALSE;
+  }     /* Next header unless yield has been set FALSE */
+
+return yield;
 }
 
 
@@ -1593,9 +1594,10 @@ for (i = 0; i < recipients_count; i++)
     s = colon + 1;
     while (isspace(*s)) s++;
 
-    parse_allow_group = TRUE;     /* Allow group syntax */
+    /* Loop for multiple addresses in the header, enabling group syntax. Note
+    that we have to reset this after the header has been scanned. */
 
-    /* Loop for multiple addresses in the header */
+    parse_allow_group = TRUE;
 
     while (*s != 0)
       {
@@ -1605,7 +1607,7 @@ for (i = 0; i < recipients_count; i++)
       int start, end, domain;
 
       /* Temporarily terminate the string at this point, and extract the
-      operative address within. */
+      operative address within, allowing group syntax. */
 
       *ss = 0;
       recipient = parse_extract_address(s,&errmess,&start,&end,&domain,FALSE);
@@ -1629,6 +1631,9 @@ for (i = 0; i < recipients_count; i++)
       s = ss + (terminator? 1:0);
       while (isspace(*s)) s++;
       }   /* Next address */
+
+    parse_allow_group = FALSE;
+    parse_found_group = FALSE;
     }     /* Next header (if found is false) */
 
   if (!found) return FAIL;
@@ -1711,13 +1716,14 @@ verify_check_header_address(uschar **user_msgptr, uschar **log_msgptr,
   uschar *pm_mailfrom, int options, int *verrno)
 {
 static int header_types[] = { htype_sender, htype_reply_to, htype_from };
+BOOL done = FALSE;
 int yield = FAIL;
 int i;
 
-for (i = 0; i < 3; i++)
+for (i = 0; i < 3 && !done; i++)
   {
   header_line *h;
-  for (h = header_list; h != NULL; h = h->next)
+  for (h = header_list; h != NULL && !done; h = h->next)
     {
     int terminator, new_ok;
     uschar *s, *ss, *endname;
@@ -1725,6 +1731,11 @@ for (i = 0; i < 3; i++)
     if (h->type != header_types[i]) continue;
     s = endname = Ustrchr(h->text, ':') + 1;
 
+    /* Scan the addresses in the header, enabling group syntax. Note that we
+    have to reset this after the header has been scanned. */
+
+    parse_allow_group = TRUE;
+
     while (*s != 0)
       {
       address_item *vaddr;
@@ -1767,11 +1778,21 @@ for (i = 0; i < 3; i++)
       else
         {
         int start, end, domain;
-        uschar *address = parse_extract_address(s, log_msgptr, &start,
-          &end, &domain, FALSE);
+        uschar *address = parse_extract_address(s, log_msgptr, &start, &end,
+          &domain, FALSE);
 
         *ss = terminator;
 
+        /* If we found an empty address, just carry on with the next one, but
+        kill the message. */
+
+        if (address == NULL && Ustrcmp(*log_msgptr, "empty address") == 0)
+          {
+          *log_msgptr = NULL;
+          s = ss;
+          continue;
+          }
+
         /* If verification failed because of a syntax error, fail this
         function, and ensure that the failing address gets added to the error
         message. */
@@ -1779,14 +1800,13 @@ for (i = 0; i < 3; i++)
         if (address == NULL)
           {
           new_ok = FAIL;
-          if (*log_msgptr != NULL)
-            {
-            while (ss > s && isspace(ss[-1])) ss--;
-            *log_msgptr = string_sprintf("syntax error in '%.*s' header when "
-              "scanning for sender: %s in \"%.*s\"",
-              endname - h->text, h->text, *log_msgptr, ss - s, s);
-            return FAIL;
-            }
+          while (ss > s && isspace(ss[-1])) ss--;
+          *log_msgptr = string_sprintf("syntax error in '%.*s' header when "
+            "scanning for sender: %s in \"%.*s\"",
+            endname - h->text, h->text, *log_msgptr, ss - s, s);
+          yield = FAIL;
+          done = TRUE;
+          break;
           }
 
         /* Else go ahead with the sender verification. But it isn't *the*
@@ -1820,15 +1840,24 @@ for (i = 0; i < 3; i++)
 
       /* Success or defer */
 
-      if (new_ok == OK) return OK;
+      if (new_ok == OK)
+        {
+        yield = OK;
+        done = TRUE;
+        break;
+        }
+
       if (new_ok == DEFER) yield = DEFER;
 
       /* Move on to any more addresses in the header */
 
       s = ss;
-      }
-    }
-  }
+      }     /* Next address */
+
+    parse_allow_group = FALSE;
+    parse_found_group = FALSE;
+    }       /* Next header, unless done */
+  }         /* Next header type unless done */
 
 if (yield == FAIL && *log_msgptr == NULL)
   *log_msgptr = US"there is no valid sender in any header line";
@@ -2223,7 +2252,7 @@ if (*t == 0)
   h.address = NULL;
   h.mx = MX_NONE;
 
-  rc = host_find_byname(&h, NULL, NULL, FALSE);
+  rc = host_find_byname(&h, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, FALSE);
   if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL)
     {
     host_item *hh;
@@ -2483,6 +2512,12 @@ else
     }
   }
 #endif
+
+/* Remove trailing period -- this is needed so that both arbitrary
+dnsbl keydomains and inverted addresses may be combined with the
+same format string, "%s.%s" */
+
+*(--bptr) = 0;
 }
 
 
@@ -2491,13 +2526,19 @@ else
 *          Perform a single dnsbl lookup         *
 *************************************************/
 
-/* This function is called from verify_check_dnsbl() below.
+/* This function is called from verify_check_dnsbl() below. It is also called
+recursively from within itself when domain and domain_txt are different
+pointers, in order to get the TXT record from the alternate domain.
 
 Arguments:
-  domain         the outer dnsbl domain (for debug message)
+  domain         the outer dnsbl domain
+  domain_txt     alternate domain to lookup TXT record on success; when the
+                   same domain is to be used, domain_txt == domain (that is,
+                   the pointers must be identical, not just the text)
   keydomain      the current keydomain (for debug message)
-  query          the domain to be looked up
-  iplist         the list of matching IP addresses
+  prepend        subdomain to lookup (like keydomain, but
+                   reversed if IP address)
+  iplist         the list of matching IP addresses, or NULL for "any"
   bitmask        true if bitmask matching is wanted
   invert_result  true if result to be inverted
   defer_return   what to return for a defer
@@ -2507,14 +2548,25 @@ Returns:         OK if lookup succeeded
 */
 
 static int
-one_check_dnsbl(uschar *domain, uschar *keydomain, uschar *query,
-  uschar *iplist, BOOL bitmask, BOOL invert_result, int defer_return)
+one_check_dnsbl(uschar *domain, uschar *domain_txt, uschar *keydomain,
+  uschar *prepend, uschar *iplist, BOOL bitmask, BOOL invert_result,
+  int defer_return)
 {
 dns_answer dnsa;
 dns_scan dnss;
 tree_node *t;
 dnsbl_cache_block *cb;
 int old_pool = store_pool;
+uschar query[256];         /* DNS domain max length */
+
+/* Construct the specific query domainname */
+
+if (!string_format(query, sizeof(query), "%s.%s", prepend, domain))
+  {
+  log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
+    "(ignored): %s...", query);
+  return FAIL;
+  }
 
 /* Look for this query in the cache. */
 
@@ -2679,8 +2731,18 @@ if (cb->rc == DNS_SUCCEED)
       }
     }
 
-  /* Either there was no IP list, or the record matched. Look up a TXT record
-  if it hasn't previously been done. */
+  /* Either there was no IP list, or the record matched, implying that the
+  domain is on the list. We now want to find a corresponding TXT record. If an
+  alternate domain is specified for the TXT record, call this function
+  recursively to look that up; this has the side effect of re-checking that
+  there is indeed an A record at the alternate domain. */
+
+  if (domain_txt != domain)
+    return one_check_dnsbl(domain_txt, domain_txt, keydomain, prepend, NULL,
+      FALSE, invert_result, defer_return);
+
+  /* If there is no alternate domain, look up a TXT record in the main domain
+  if it has not previously been cached. */
 
   if (!cb->text_set)
     {
@@ -2751,7 +2813,7 @@ given, comma-separated, for example: x.y.z=127.0.0.1,127.0.0.2.
 
 If no key is given, what is looked up in the domain is the inverted IP address
 of the current client host. If a key is given, it is used to construct the
-domain for the lookup. For example,
+domain for the lookup. For example:
 
   dsn.rfc-ignorant.org/$sender_address_domain
 
@@ -2760,6 +2822,17 @@ then we check for a TXT record for an error message, and if found, save its
 value in $dnslist_text. We also cache everything in a tree, to optimize
 multiple lookups.
 
+The TXT record is normally looked up in the same domain as the A record, but
+when many lists are combined in a single DNS domain, this will not be a very
+specific message. It is possible to specify a different domain for looking up
+TXT records; this is given before the main domain, comma-separated. For
+example:
+
+  dnslists = http.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.2 : \
+             socks.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.3
+
+The caching ensures that only one lookup in dnsbl.sorbs.net is done.
+
 Note: an address for testing RBL is 192.203.178.39
 Note: an address for testing DUL is 192.203.178.4
 Note: a domain for testing RFCI is example.tld.dsn.rfc-ignorant.org
@@ -2784,7 +2857,6 @@ uschar *list = *listptr;
 uschar *domain;
 uschar *s;
 uschar buffer[1024];
-uschar query[256];         /* DNS domain max length */
 uschar revadd[128];        /* Long enough for IPv6 address */
 
 /* Indicate that the inverted IP address is not yet set up */
@@ -2800,8 +2872,9 @@ dns_init(FALSE, FALSE);
 while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL)
   {
   int rc;
-  BOOL frc;
   BOOL bitmask = FALSE;
+  uschar *domain_txt;
+  uschar *comma;
   uschar *iplist;
   uschar *key;
 
@@ -2846,6 +2919,18 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
     *iplist++ = 0;
     }
 
+  /* If there is a comma in the domain, it indicates that a second domain for
+  looking up TXT records is provided, before the main domain. Otherwise we must
+  set domain_txt == domain. */
+
+  domain_txt = domain;
+  comma = Ustrchr(domain, ',');
+  if (comma != NULL)
+    {
+    *comma++ = 0;
+    domain = comma;
+    }
+
   /* Check that what we have left is a sensible domain name. There is no reason
   why these domains should in fact use the same syntax as hosts and email
   domains, but in practice they seem to. However, there is little point in
@@ -2862,6 +2947,18 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
       }
     }
 
+  /* Check the alternate domain if present */
+
+  if (domain_txt != domain) for (s = domain_txt; *s != 0; s++)
+    {
+    if (!isalnum(*s) && *s != '-' && *s != '.')
+      {
+      log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
+        "strange characters - is this right?", domain_txt);
+      break;
+      }
+    }
+
   /* If there is no key string, construct the query by adding the domain name
   onto the inverted host address, and perform a single DNS lookup. */
 
@@ -2869,25 +2966,14 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
     {
     if (sender_host_address == NULL) return FAIL;    /* can never match */
     if (revadd[0] == 0) invert_address(revadd, sender_host_address);
-    frc = string_format(query, sizeof(query), "%s%s", revadd, domain);
-
-    if (!frc)
-      {
-      log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
-        "(ignored): %s...", query);
-      continue;
-      }
-
-    rc = one_check_dnsbl(domain, sender_host_address, query, iplist, bitmask,
-      invert_result, defer_return);
-
+    rc = one_check_dnsbl(domain, domain_txt, sender_host_address, revadd,
+      iplist, bitmask, invert_result, defer_return);
     if (rc == OK)
       {
-      dnslist_domain = string_copy(domain);
+      dnslist_domain = string_copy(domain_txt);
       HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
-        sender_host_address, domain);
+        sender_host_address, dnslist_domain);
       }
-
     if (rc != FAIL) return rc;     /* OK or DEFER */
     }
 
@@ -2900,36 +2986,27 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
     BOOL defer = FALSE;
     uschar *keydomain;
     uschar keybuffer[256];
+    uschar keyrevadd[128];
 
     while ((keydomain = string_nextinlist(&key, &keysep, keybuffer,
             sizeof(keybuffer))) != NULL)
       {
+      uschar *prepend = keydomain;
+
       if (string_is_ip_address(keydomain, NULL) != 0)
         {
-        uschar keyrevadd[128];
         invert_address(keyrevadd, keydomain);
-        frc = string_format(query, sizeof(query), "%s%s", keyrevadd, domain);
-        }
-      else
-        {
-        frc = string_format(query, sizeof(query), "%s.%s", keydomain, domain);
-        }
-
-      if (!frc)
-        {
-        log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
-          "(ignored): %s...", query);
-        continue;
+        prepend = keyrevadd;
         }
 
-      rc = one_check_dnsbl(domain, keydomain, query, iplist, bitmask,
-        invert_result, defer_return);
+      rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist,
+        bitmask, invert_result, defer_return);
 
       if (rc == OK)
         {
-        dnslist_domain = string_copy(domain);
+        dnslist_domain = string_copy(domain_txt);
         HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
-          keydomain, domain);
+          keydomain, dnslist_domain);
         return OK;
         }