Basic cutthrough delivery.
[exim.git] / src / src / verify.c
index 23f63d803b997dcdcf2cd3631fc47be64940a88c..ff18847e7ba37708f1804f887da8b20b40bf2bb2 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/verify.c,v 1.50 2007/05/08 13:08:22 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2007 */
+/* Copyright (c) University of Cambridge 1995 - 2009 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions concerned with verifying things. The original code for callout
@@ -13,6 +11,9 @@ caching was contributed by Kevin Fleming (but I hacked it around a bit). */
 
 #include "exim.h"
 
+#define CUTTHROUGH_CMD_TIMEOUT  30     /* timeout for cutthrough-routing calls */
+#define CUTTHROUGH_DATA_TIMEOUT 60     /* timeout for cutthrough-routing calls */
+address_item cutthrough_addr;
 
 /* Structure for caching DNSBL lookups */
 
@@ -475,7 +476,8 @@ for (host = host_list; host != NULL && !done; host = host->next)
   set the error for the last one. Use the callout_connect timeout. */
 
   inblock.sock = outblock.sock =
-    smtp_connect(host, host_af, port, interface, callout_connect, TRUE);
+    smtp_connect(host, host_af, port, interface, callout_connect, TRUE, NULL);
+  /* reconsider DSCP here */
   if (inblock.sock < 0)
     {
     addr->message = string_sprintf("could not connect to %s [%s]: %s",
@@ -642,6 +644,9 @@ for (host = host_list; host != NULL && !done; host = host->next)
 
       if (done && pm_mailfrom != NULL)
         {
+        /*XXX not suitable for cutthrough - sequencing problems */
+       cutthrough_delivery= FALSE;
+
         done =
           smtp_write_command(&outblock, FALSE, "RSET\r\n") >= 0 &&
           smtp_read_response(&inblock, responsebuffer,
@@ -734,8 +739,32 @@ 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");
-  (void)close(inblock.sock);
+  /*XXX cutthrough - if "done"
+  and "yeild" is OK
+  and we have no cutthrough conn so far
+  here is where we want to leave the conn open */
+  /* and leave some form of marker for it */
+  /*XXX in fact for simplicity we should abandon cutthrough as soon as more than one address
+  comes into play */
+/*XXX what about TLS? */
+  if (  cutthrough_delivery
+     && done
+     && yield == OK
+     && cutthrough_fd < 0
+     && (options & (vopt_callout_recipsender|vopt_callout_recippmaster)) == vopt_callout_recipsender
+     && !random_local_part
+     && !pm_mailfrom
+     )
+    {
+    cutthrough_fd= outblock.sock;      /* We assume no buffer in use in the outblock */
+    cutthrough_addr= *addr;            /* Save the address_item for later logging */
+    }
+  else
+    {
+    if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
+    (void)close(inblock.sock);
+    }
+
   }    /* Loop through all hosts, while !done */
 
 /* If we get here with done == TRUE, a successful callout happened, and yield
@@ -828,6 +857,218 @@ return yield;
 
 
 
+void
+open_cutthrough_connection( address_item * addr )
+{
+address_item addr2;
+
+/* Use a recipient-verify-callout to set up the cutthrough connection. */
+/* We must use a copy of the address for verification, because it might
+get rewritten. */
+
+addr2 = *addr;
+HDEBUG(D_acl) debug_printf("----------- start cutthrough setup ------------\n");
+(void) verify_address(&addr2, NULL,
+       vopt_is_recipient | vopt_callout_recipsender | vopt_callout_no_cache,
+       CUTTHROUGH_CMD_TIMEOUT, -1, -1,
+       NULL, NULL, NULL);
+HDEBUG(D_acl) debug_printf("----------- end cutthrough setup ------------\n");
+return;
+}
+
+
+static smtp_outblock ctblock;
+uschar ctbuffer[8192];
+
+
+void
+cancel_cutthrough_connection( void )
+{
+ctblock.ptr = ctbuffer;
+cutthrough_delivery= FALSE;
+if(cutthrough_fd >= 0)    /*XXX get that initialised, also at RSET */
+  {
+  int rc;
+
+  /* We could be sending this after a bunch of data, but that is ok as
+     the only way to cancel the transfer in dataphase is to drop the tcp
+     conn before the final dot.
+  */
+  HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> QUIT\n");
+  rc= send(cutthrough_fd, "QUIT\r\n", 6, 0);
+  /*XXX error handling?   TLS?   See flush_buffer() in smtp_out.c */
+
+  (void)close(cutthrough_fd);
+  cutthrough_fd= -1;
+  HDEBUG(D_acl) debug_printf("----------- cutthrough shutdown ------------\n");
+  }
+}
+
+
+
+/* Buffered output counted data block.   Return boolean success */
+BOOL
+cutthrough_puts(uschar * cp, int n)
+{
+if(cutthrough_fd >= 0)
+  while(n--)
+  {
+  /*XXX TLS?   See flush_buffer() in smtp_out.c */
+
+  if(ctblock.ptr >= ctblock.buffer+ctblock.buffersize)
+    {
+    if(send(cutthrough_fd, ctblock.buffer, ctblock.buffersize, 0) < 0)
+       goto bad;
+    transport_count += ctblock.buffersize;
+    ctblock.ptr= ctblock.buffer;
+    }
+
+  *ctblock.ptr++ = *cp++;
+  }
+return TRUE;
+
+bad:
+cancel_cutthrough_connection();
+return FALSE;
+}
+
+BOOL
+cutthrough_flush_send( void )
+{
+if(cutthrough_fd >= 0)
+  {
+  if(send(cutthrough_fd, ctblock.buffer, ctblock.ptr-ctblock.buffer, 0) < 0)
+    goto bad;
+  transport_count += ctblock.ptr-ctblock.buffer;
+  ctblock.ptr= ctblock.buffer;
+  }
+return TRUE;
+
+bad:
+cancel_cutthrough_connection();
+return FALSE;
+}
+
+
+BOOL
+cutthrough_put_nl( void )
+{
+return cutthrough_puts(US"\r\n", 2);
+}
+
+
+/* Get and check response from cutthrough target */
+static uschar
+cutthrough_response(char expect, uschar ** copy)
+{
+smtp_inblock inblock;
+uschar inbuffer[4096];
+uschar responsebuffer[4096];
+
+inblock.buffer = inbuffer;
+inblock.buffersize = sizeof(inbuffer);
+inblock.ptr = inbuffer;
+inblock.ptrend = inbuffer;
+inblock.sock = cutthrough_fd;
+if(!smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), expect, CUTTHROUGH_DATA_TIMEOUT))
+  cancel_cutthrough_connection();
+
+if(copy != NULL)
+  {
+  uschar * cp;
+  *copy= cp= string_copy(responsebuffer);
+  /* Trim the trailing end of line */
+  cp += Ustrlen(responsebuffer);
+  if(cp > *copy  &&  cp[-1] == '\n') *--cp = '\0';
+  if(cp > *copy  &&  cp[-1] == '\r') *--cp = '\0';
+  }
+
+return responsebuffer[0];
+}
+
+
+/* Negotiate dataphase with the cutthrough target, returning success boolean */
+BOOL
+cutthrough_predata( void )
+{
+int rc;
+
+if(cutthrough_fd < 0)
+  return FALSE;
+
+HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> DATA\n");
+rc= send(cutthrough_fd, "DATA\r\n", 6, 0);
+if (rc <= 0)
+  {
+  HDEBUG(D_transport|D_acl) debug_printf("send failed: %s\n", strerror(errno));
+  cancel_cutthrough_connection();
+  return FALSE;
+  }
+/*XXX error handling?   TLS?   See flush_buffer() in smtp_out.c */
+
+/* Assume nothing buffered.  If it was it gets ignored. */
+return cutthrough_response('3', NULL) == '3';
+}
+
+
+/* Buffered send of headers.  Return success boolean. */
+/* Also sends header-terminating blank line.          */
+/* Sets up the "ctblock" buffer as a side-effect.     */
+BOOL
+cutthrough_headers_send( void )
+{
+header_line * h;
+
+if(cutthrough_fd < 0)
+  return FALSE;
+
+ctblock.buffer = ctbuffer;
+ctblock.buffersize = sizeof(ctbuffer);
+ctblock.ptr = ctbuffer;
+/* ctblock.cmd_count = 0; ctblock.authenticating = FALSE; */
+ctblock.sock = cutthrough_fd;
+
+for(h= header_list; h != NULL; h= h->next)
+  if(h->type != htype_old  &&  h->text != NULL)
+    if(!cutthrough_puts(h->text, h->slen))
+      return FALSE;
+
+if(!cutthrough_put_nl())
+  return TRUE;
+}
+
+
+/* Have senders final-dot.  Send one to cutthrough target, and grab the response.
+   Log an OK response as a transmission.
+   Return smtp response-class digit.
+   XXX where do fail responses from target get logged?
+*/
+uschar *
+cutthrough_finaldot( void )
+{
+HDEBUG(D_transport|D_acl|D_v) debug_printf("  SMTP>> .\n");
+
+/* Assume data finshed with new-line */
+if(!cutthrough_puts(US".", 1) || !cutthrough_put_nl()
+  || !cutthrough_flush_send()
+  || cutthrough_response('2', &cutthrough_addr.message) != '2')
+  return cutthrough_addr.message;
+
+(void)close(cutthrough_fd);
+cutthrough_fd= -1;
+HDEBUG(D_acl) debug_printf("----------- cutthrough close ------------\n");
+
+delivery_log(&cutthrough_addr, (int)'>');
+/* C= ok */
+/* QT ok */
+/* DT always 0? */
+/* delivery S= zero!  (transport_count) */
+/* not TLS yet hence no X, CV, DN */
+
+return cutthrough_addr.message;
+}
+
+
 /*************************************************
 *           Copy error to toplevel address       *
 *************************************************/
@@ -863,6 +1104,42 @@ return yield;
 
 
 
+/**************************************************
+* printf that automatically handles TLS if needed *
+***************************************************/
+
+/* This function is used by verify_address() as a substitute for all fprintf()
+calls; a direct fprintf() will not produce output in a TLS SMTP session, such
+as a response to an EXPN command.  smtp_in.c makes smtp_printf available but
+that assumes that we always use the smtp_out FILE* when not using TLS or the
+ssl buffer when we are.  Instead we take a FILE* parameter and check to see if
+that is smtp_out; if so, smtp_printf() with TLS support, otherwise regular
+fprintf().
+
+Arguments:
+  f           the candidate FILE* to write to
+  format      format string
+  ...         optional arguments
+
+Returns:
+              nothing
+*/
+
+static void PRINTF_FUNCTION(2,3)
+respond_printf(FILE *f, const char *format, ...)
+{
+va_list ap;
+
+va_start(ap, format);
+if (smtp_out && (f == smtp_out))
+  smtp_vprintf(format, ap);
+else
+  vfprintf(f, format, ap);
+va_end(ap);
+}
+
+
+
 /*************************************************
 *            Verify an email address             *
 *************************************************/
@@ -962,8 +1239,8 @@ if (parse_find_at(address) == NULL)
   if ((options & vopt_qualify) == 0)
     {
     if (f != NULL)
-      fprintf(f, "%sA domain is required for \"%s\"%s\n", ko_prefix, address,
-        cr);
+      respond_printf(f, "%sA domain is required for \"%s\"%s\n",
+        ko_prefix, address, cr);
     *failure_ptr = US"qualify";
     return FAIL;
     }
@@ -1227,25 +1504,27 @@ while (addr_new != NULL)
       {
       address_item *p = addr->parent;
 
-      fprintf(f, "%s%s %s", ko_prefix, full_info? addr->address : address,
+      respond_printf(f, "%s%s %s", ko_prefix,
+        full_info? addr->address : address,
         address_test_mode? "is undeliverable" : "failed to verify");
       if (!expn && admin_user)
         {
         if (addr->basic_errno > 0)
-          fprintf(f, ": %s", strerror(addr->basic_errno));
+          respond_printf(f, ": %s", strerror(addr->basic_errno));
         if (addr->message != NULL)
-          fprintf(f, ": %s", addr->message);
+          respond_printf(f, ": %s", addr->message);
         }
 
       /* Show parents iff doing full info */
 
       if (full_info) while (p != NULL)
         {
-        fprintf(f, "%s\n    <-- %s", cr, p->address);
+        respond_printf(f, "%s\n    <-- %s", cr, p->address);
         p = p->parent;
         }
-      fprintf(f, "%s\n", cr);
+      respond_printf(f, "%s\n", cr);
       }
+    cancel_cutthrough_connection();
 
     if (!full_info) return copy_error(vaddr, addr, FAIL);
       else yield = FAIL;
@@ -1259,27 +1538,29 @@ while (addr_new != NULL)
     if (f != NULL)
       {
       address_item *p = addr->parent;
-      fprintf(f, "%s%s cannot be resolved at this time", ko_prefix,
+      respond_printf(f, "%s%s cannot be resolved at this time", ko_prefix,
         full_info? addr->address : address);
       if (!expn && admin_user)
         {
         if (addr->basic_errno > 0)
-          fprintf(f, ": %s", strerror(addr->basic_errno));
+          respond_printf(f, ": %s", strerror(addr->basic_errno));
         if (addr->message != NULL)
-          fprintf(f, ": %s", addr->message);
+          respond_printf(f, ": %s", addr->message);
         else if (addr->basic_errno <= 0)
-          fprintf(f, ": unknown error");
+          respond_printf(f, ": unknown error");
         }
 
       /* Show parents iff doing full info */
 
       if (full_info) while (p != NULL)
         {
-        fprintf(f, "%s\n    <-- %s", cr, p->address);
+        respond_printf(f, "%s\n    <-- %s", cr, p->address);
         p = p->parent;
         }
-      fprintf(f, "%s\n", cr);
+      respond_printf(f, "%s\n", cr);
       }
+    cancel_cutthrough_connection();
+
     if (!full_info) return copy_error(vaddr, addr, DEFER);
       else if (yield == OK) yield = DEFER;
     }
@@ -1293,16 +1574,16 @@ while (addr_new != NULL)
     if (addr_new == NULL)
       {
       if (addr_local == NULL && addr_remote == NULL)
-        fprintf(f, "250 mail to <%s> is discarded\r\n", address);
+        respond_printf(f, "250 mail to <%s> is discarded\r\n", address);
       else
-        fprintf(f, "250 <%s>\r\n", address);
+        respond_printf(f, "250 <%s>\r\n", address);
       }
     else while (addr_new != NULL)
       {
       address_item *addr2 = addr_new;
       addr_new = addr2->next;
       if (addr_new == NULL) ok_prefix = US"250 ";
-      fprintf(f, "%s<%s>\r\n", ok_prefix, addr2->address);
+      respond_printf(f, "%s<%s>\r\n", ok_prefix, addr2->address);
       }
     return OK;
     }
@@ -2219,7 +2500,7 @@ if (iplookup)
     }
   else   /* Single-key style */
     {
-    int sep = (Ustrcmp(lookup_list[search_type].name, "iplsearch") == 0)?
+    int sep = (Ustrcmp(lookup_list[search_type]->name, "iplsearch") == 0)?
       ':' : '.';
     insize = host_aton(cb->host_address, incoming);
     host_mask(insize, incoming, mlen);
@@ -2476,16 +2757,18 @@ return verify_check_this_host(listptr, sender_host_cache, NULL,
 
 
 /*************************************************
-*    Invert an IP address for a DNS black list   *
+*              Invert an IP address              *
 *************************************************/
 
-/*
+/* Originally just used for DNS xBL lists, now also used for the
+reverse_ip expansion operator.
+
 Arguments:
   buffer         where to put the answer
   address        the address to invert
 */
 
-static void
+void
 invert_address(uschar *buffer, uschar *address)
 {
 int bin[4];
@@ -2999,7 +3282,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
 
   for (s = domain; *s != 0; s++)
     {
-    if (!isalnum(*s) && *s != '-' && *s != '.')
+    if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
       {
       log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
         "strange characters - is this right?", domain);
@@ -3011,7 +3294,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
 
   if (domain_txt != domain) for (s = domain_txt; *s != 0; s++)
     {
-    if (!isalnum(*s) && *s != '-' && *s != '.')
+    if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_')
       {
       log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains "
         "strange characters - is this right?", domain_txt);
@@ -3031,6 +3314,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
     if (rc == OK)
       {
       dnslist_domain = string_copy(domain_txt);
+      dnslist_matched = string_copy(sender_host_address);
       HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
         sender_host_address, dnslist_domain);
       }
@@ -3065,6 +3349,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL
       if (rc == OK)
         {
         dnslist_domain = string_copy(domain_txt);
+        dnslist_matched = string_copy(keydomain);
         HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n",
           keydomain, dnslist_domain);
         return OK;