Update copyright year in (most) files (those that my script finds).
[exim.git] / src / src / verify.c
index 458f4d97849418db6a79c240787ff177f355a219..117cf81f8bb698cb9453427c386e15081073a722 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/verify.c,v 1.15 2005/02/17 11:58:26 ph10 Exp $ */
+/* $Cambridge: exim/src/src/verify.c,v 1.31 2006/02/07 11:19:00 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2006 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with verifying things. The original code for callout
@@ -128,6 +128,7 @@ Arguments:
   options           the verification options - these bits are used:
                       vopt_is_recipient => this is a recipient address
                       vopt_callout_no_cache => don't use callout cache
+                      vopt_callout_fullpm => if postmaster check, do full one
                       vopt_callout_random => do the "random" thing
                       vopt_callout_recipsender => use real sender for recipient
                       vopt_callout_recippmaster => use postmaster for recipient
@@ -151,6 +152,7 @@ BOOL done = FALSE;
 uschar *address_key;
 uschar *from_address;
 uschar *random_local_part = NULL;
+uschar *save_deliver_domain = deliver_domain;
 uschar **failure_ptr = is_recipient?
   &recipient_verify_failure : &sender_verify_failure;
 open_db dbblock;
@@ -408,18 +410,24 @@ for (host = host_list; host != NULL && !done; host = host->next)
 
   host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6;
 
-  /* Expand and interpret the interface and port strings. This has to
-  be delayed till now, because they may expand differently for different
-  hosts. If there's a failure, log it, but carry on with the defaults. */
+  /* Expand and interpret the interface and port strings. The latter will not
+  be used if there is a host-specific port (e.g. from a manualroute router).
+  This has to be delayed till now, because they may expand differently for
+  different hosts. If there's a failure, log it, but carry on with the
+  defaults. */
 
   deliver_host = host->name;
   deliver_host_address = host->address;
+  deliver_domain = addr->domain;
+
   if (!smtp_get_interface(tf->interface, host_af, addr, NULL, &interface,
           US"callout") ||
       !smtp_get_port(tf->port, addr, &port, US"callout"))
     log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
       addr->message);
+
   deliver_host = deliver_host_address = NULL;
+  deliver_domain = save_deliver_domain;
 
   /* Set HELO string according to the protocol */
 
@@ -536,7 +544,8 @@ for (host = host_list; host != NULL && !done; host = host->next)
           smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
             '2', callout) &&
 
-          smtp_write_command(&outblock, FALSE, "MAIL FROM:<>\r\n") >= 0 &&
+          smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n",
+            from_address) >= 0 &&
           smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
             '2', callout);
         }
@@ -548,9 +557,14 @@ for (host = host_list; host != NULL && !done; host = host->next)
 
     if (new_domain_record.random_result != ccache_accept && done)
       {
+      /* Get the rcpt_include_affixes flag from the transport if there is one,
+      but assume FALSE if there is not. */
+
       done =
         smtp_write_command(&outblock, FALSE, "RCPT TO:<%.1000s>\r\n",
-          addr->address) >= 0 &&
+          transport_rcpt_address(addr,
+            (addr->transport == NULL)? FALSE :
+             addr->transport->rcpt_include_affixes)) >= 0 &&
         smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer),
           '2', callout);
 
@@ -562,7 +576,8 @@ for (host = host_list; host != NULL && !done; host = host->next)
         new_address_record.result = ccache_reject;
         }
 
-      /* Do postmaster check if requested */
+      /* Do postmaster check if requested; if a full check is required, we
+      check for RCPT TO:<postmaster> (no domain) in accordance with RFC 821. */
 
       if (done && pm_mailfrom != NULL)
         {
@@ -576,10 +591,29 @@ for (host = host_list; host != NULL && !done; host = host->next)
           smtp_read_response(&inblock, responsebuffer,
             sizeof(responsebuffer), '2', callout) &&
 
+          /* First try using the current domain */
+
+          ((
           smtp_write_command(&outblock, FALSE,
             "RCPT TO:<postmaster@%.1000s>\r\n", addr->domain) >= 0 &&
           smtp_read_response(&inblock, responsebuffer,
-            sizeof(responsebuffer), '2', callout);
+            sizeof(responsebuffer), '2', callout)
+          )
+
+          ||
+
+          /* If that doesn't work, and a full check is requested,
+          try without the domain. */
+
+          (
+          (options & vopt_callout_fullpm) != 0 &&
+          smtp_write_command(&outblock, FALSE,
+            "RCPT TO:<postmaster>\r\n") >= 0 &&
+          smtp_read_response(&inblock, responsebuffer,
+            sizeof(responsebuffer), '2', callout)
+          ));
+
+        /* Sort out the cache record */
 
         new_domain_record.postmaster_stamp = time(NULL);
 
@@ -593,7 +627,7 @@ for (host = host_list; host != NULL && !done; host = host->next)
           }
         }
       }           /* Random not accepted */
-    }             /* MAIL FROM:<> accepted */
+    }             /* MAIL FROM: accepted */
 
   /* For any failure of the main check, other than a negative response, we just
   close the connection and carry on. We can identify a negative response by the
@@ -640,7 +674,7 @@ for (host = host_list; host != NULL && !done; host = host->next)
   /* End the SMTP conversation and close the connection. */
 
   if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
-  close(inblock.sock);
+  (void)close(inblock.sock);
   }    /* Loop through all hosts, while !done */
 
 /* If we get here with done == TRUE, a successful callout happened, and yield
@@ -649,7 +683,7 @@ Otherwise, we looped through the hosts but couldn't complete the business.
 However, there may be domain-specific information to cache in both cases.
 
 The value of the result field in the new_domain record is ccache_unknown if
-there was an error before or with MAIL FROM:<>, and errno was not zero,
+there was an error before or with MAIL FROM:, and errno was not zero,
 implying some kind of I/O error. We don't want to write the cache in that case.
 Otherwise the value is ccache_accept or ccache_reject. */
 
@@ -786,10 +820,13 @@ Arguments:
                        rewriting and messages from callouts
                      vopt_qualify => qualify an unqualified address; else error
                      vopt_expn => called from SMTP EXPN command
+                     vopt_success_on_redirect => when a new address is generated
+                       the verification instantly succeeds
 
                      These ones are used by do_callout() -- the options variable
                        is passed to it.
 
+                     vopt_callout_fullpm => if postmaster check, do full one
                      vopt_callout_no_cache => don't use callout cache
                      vopt_callout_random => do the "random" thing
                      vopt_callout_recipsender => use real sender for recipient
@@ -822,6 +859,7 @@ BOOL allok = TRUE;
 BOOL full_info = (f == NULL)? FALSE : (debug_selector != 0);
 BOOL is_recipient = (options & vopt_is_recipient) != 0;
 BOOL expn         = (options & vopt_expn) != 0;
+BOOL success_on_redirect = (options & vopt_success_on_redirect) != 0;
 int i;
 int yield = OK;
 int verify_type = expn? v_expn :
@@ -1022,13 +1060,16 @@ while (addr_new != NULL)
         if (tf.hosts != NULL && (host_list == NULL || tf.hosts_override))
           {
           uschar *s;
+          uschar *save_deliver_domain = deliver_domain;
+          uschar *save_deliver_localpart = deliver_localpart;
 
           host_list = NULL;    /* Ignore the router's hosts */
 
           deliver_domain = addr->domain;
           deliver_localpart = addr->local_part;
           s = expand_string(tf.hosts);
-          deliver_domain = deliver_localpart = NULL;
+          deliver_domain = save_deliver_domain;
+          deliver_localpart = save_deliver_localpart;
 
           if (s == NULL)
             {
@@ -1052,7 +1093,7 @@ while (addr_new != NULL)
               {
               nexthost = host->next;
               if (tf.gethostbyname ||
-                  string_is_ip_address(host->name, NULL) > 0)
+                  string_is_ip_address(host->name, NULL) != 0)
                 (void)host_find_byname(host, NULL, &canonical_name, TRUE);
               else
                 {
@@ -1191,9 +1232,12 @@ while (addr_new != NULL)
     generated address. */
 
     if (!full_info &&                    /* Stop if short info wanted AND */
-         (addr_new == NULL ||            /* No new address OR */
-          addr_new->next != NULL ||      /* More than one new address OR */
-          testflag(addr_new, af_pfr)))   /* New address is pfr */
+         (((addr_new == NULL ||          /* No new address OR */
+           addr_new->next != NULL ||     /* More than one new address OR */
+           testflag(addr_new, af_pfr)))  /* New address is pfr */
+         ||                              /* OR */
+         (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", address,
         address_test_mode? "is deliverable" : "verified");
@@ -1228,6 +1272,10 @@ else for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++)
     addr_list = addr->next;
 
     fprintf(f, "%s", CS addr->address);
+#ifdef EXPERIMENTAL_SRS
+    if(addr->p.srs_sender)
+      fprintf(f, "    [srs = %s]", addr->p.srs_sender);
+#endif
     while (p != NULL)
       {
       fprintf(f, "\n    <-- %s", p->address);
@@ -1407,6 +1455,89 @@ return OK;
 
 
 
+/*************************************************
+*          Check for blind recipients            *
+*************************************************/
+
+/* This function checks that every (envelope) recipient is mentioned in either
+the To: or Cc: header lines, thus detecting blind carbon copies.
+
+There are two ways of scanning that could be used: either scan the header lines
+and tick off the recipients, or scan the recipients and check the header lines.
+The original proposed patch did the former, but I have chosen to do the latter,
+because (a) it requires no memory and (b) will use fewer resources when there
+are many addresses in To: and/or Cc: and only one or two envelope recipients.
+
+Arguments:   none
+Returns:     OK    if there are no blind recipients
+             FAIL  if there is at least one blind recipient
+*/
+
+int
+verify_check_notblind(void)
+{
+int i;
+for (i = 0; i < recipients_count; i++)
+  {
+  header_line *h;
+  BOOL found = FALSE;
+  uschar *address = recipients_list[i].address;
+
+  for (h = header_list; !found && h != NULL; h = h->next)
+    {
+    uschar *colon, *s;
+
+    if (h->type != htype_to && h->type != htype_cc) continue;
+
+    colon = Ustrchr(h->text, ':');
+    s = colon + 1;
+    while (isspace(*s)) s++;
+
+    parse_allow_group = TRUE;     /* Allow group syntax */
+
+    /* Loop for multiple addresses in the header */
+
+    while (*s != 0)
+      {
+      uschar *ss = parse_find_address_end(s, FALSE);
+      uschar *recipient,*errmess;
+      int terminator = *ss;
+      int start, end, domain;
+
+      /* Temporarily terminate the string at this point, and extract the
+      operative address within. */
+
+      *ss = 0;
+      recipient = parse_extract_address(s,&errmess,&start,&end,&domain,FALSE);
+      *ss = terminator;
+
+      /* If we found a valid recipient that has a domain, compare it with the
+      envelope recipient. Local parts are compared case-sensitively, domains
+      case-insensitively. By comparing from the start with length "domain", we
+      include the "@" at the end, which ensures that we are comparing the whole
+      local part of each address. */
+
+      if (recipient != NULL && domain != 0)
+        {
+        found = Ustrncmp(recipient, address, domain) == 0 &&
+                strcmpic(recipient + domain, address + domain) == 0;
+        if (found) break;
+        }
+
+      /* Advance to the next address */
+
+      s = ss + (terminator? 1:0);
+      while (isspace(*s)) s++;
+      }   /* Next address */
+    }     /* Next header (if found is false) */
+
+  if (!found) return FAIL;
+  }       /* Next recipient */
+
+return OK;
+}
+
+
 
 /*************************************************
 *          Find if verified sender               *
@@ -1765,7 +1896,7 @@ sender_ident = string_printing(string_copyn(p, 127));
 DEBUG(D_ident) debug_printf("sender_ident = %s\n", sender_ident);
 
 END_OFF:
-close(sock);
+(void)close(sock);
 return;
 }
 
@@ -1788,25 +1919,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 "*". */
@@ -1820,12 +1960,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;
@@ -1838,76 +1983,109 @@ if (*ss == '@')
 /* If the pattern is an IP address, optionally followed by a bitmask count, do
 a (possibly masked) comparision with the current IP address. */
 
-if (string_is_ip_address(ss, &maskoffset) > 0)
+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 */
+
+semicolon = Ustrchr(ss, ';');
 
-if (Ustrncmp(ss, "net", 3) == 0 && (semicolon = Ustrchr(ss, ';')) != NULL)
+/* If we are doing an IP address only match, then all lookups must be IP
+address lookups, even if there is no "net-". */
+
+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. We obey this code even if we
+have already set iplookup, so as to skip over the "net-" prefix and to set the
+mask length. 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;
+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 query-style with a file
+  name, we have to fish the file off the start of 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_absfilequery))
+    {
+    filename = semicolon + 1;
+    key = filename;
+    while (*key != 0 && !isspace(*key)) key++;
+    filename = string_copyn(filename, key - filename);
+    while (isspace(*key)) key++;
     }
+  else 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 != '_' &&
@@ -1931,9 +2109,7 @@ if (*t == 0)
     host_item *hh;
     for (hh = &h; hh != NULL; hh = hh->next)
       {
-      if (Ustrcmp(hh->address, (Ustrchr(hh->address, ':') == NULL)?
-        cb->host_ipv4 : cb->host_address) == 0)
-          return OK;
+      if (host_is_in_net(hh->address, cb->host_address, 0)) return OK;
       }
     return FAIL;
     }
@@ -1972,7 +2148,7 @@ if ((semicolon = Ustrchr(ss, ';')) != NULL)
       search_error_message, ss);
     return DEFER;
     }
-  isquery = mac_islookup(id, lookup_querystyle);
+  isquery = mac_islookup(id, lookup_querystyle|lookup_absfilequery);
   }
 
 if (isquery)
@@ -2608,7 +2784,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
     while ((keydomain = string_nextinlist(&key, &keysep, keybuffer,
             sizeof(keybuffer))) != NULL)
       {
-      if (string_is_ip_address(keydomain, NULL) > 0)
+      if (string_is_ip_address(keydomain, NULL) != 0)
         {
         uschar keyrevadd[128];
         invert_address(keyrevadd, keydomain);