Magnus' patch for $sending_ip_address and $sending_port.
[exim.git] / src / src / transports / smtp.c
index ac013470b22ab0963467d02688372e99504b2215..cf09010cbeee96368f10e9e651db5d5672f6e91d 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/transports/smtp.c,v 1.28 2006/10/30 16:41:04 ph10 Exp $ */
+/* $Cambridge: exim/src/src/transports/smtp.c,v 1.33 2007/01/30 15:10:59 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. */
 
 #include "../exim.h"
@@ -21,6 +21,8 @@ before the lower case letters). Some live in the transport_instance block so as
 to be publicly visible; these are flagged with opt_public. */
 
 optionlist smtp_transport_options[] = {
+  { "address_retry_include_sender", opt_bool,
+      (void *)offsetof(smtp_transport_options_block, address_retry_include_sender) },
   { "allow_localhost",      opt_bool,
       (void *)offsetof(smtp_transport_options_block, allow_localhost) },
   { "authenticated_sender", opt_stringptr,
@@ -61,6 +63,14 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, final_timeout) },
   { "gethostbyname",        opt_bool,
       (void *)offsetof(smtp_transport_options_block, gethostbyname) },
+  #ifdef SUPPORT_TLS
+  { "gnutls_require_kx",    opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, gnutls_require_kx) },
+  { "gnutls_require_mac",   opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, gnutls_require_mac) },
+  { "gnutls_require_protocols", opt_stringptr,
+      (void *)offsetof(smtp_transport_options_block, gnutls_require_proto) },
+  #endif
   { "helo_data",            opt_stringptr,
       (void *)offsetof(smtp_transport_options_block, helo_data) },
   { "hosts",                opt_stringptr,
@@ -159,6 +169,7 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   1024,                /* size_addition */
   5,                   /* hosts_max_try */
   50,                  /* hosts_max_try_hardlimit */
+  TRUE,                /* address_retry_include_sender */
   FALSE,               /* allow_localhost */
   FALSE,               /* authenticated_sender_force */
   FALSE,               /* gethostbyname */
@@ -175,6 +186,9 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   NULL,                /* tls_crl */
   NULL,                /* tls_privatekey */
   NULL,                /* tls_require_ciphers */
+  NULL,                /* gnutls_require_kx */
+  NULL,                /* gnutls_require_mac */
+  NULL,                /* gnutls_require_proto */
   NULL,                /* tls_verify_certificates */
   TRUE                 /* tls_tempfail_tryclear */
   #endif
@@ -555,19 +569,21 @@ subsequent general error, it will get reset accordingly. If not, it will get
 converted to OK at the end.
 
 Arguments:
-  addrlist         the complete address list
-  include_affixes  TRUE if affixes include in RCPT
-  sync_addr        ptr to the ptr of the one to start scanning at (updated)
-  host             the host we are connected to
-  count            the number of responses to read
-  pending_MAIL     true if the first response is for MAIL
-  pending_DATA     0 if last command sent was not DATA
-                  +1 if previously had a good recipient
-                  -1 if not previously had a good recipient
-  inblock          incoming SMTP block
-  timeout          timeout value
-  buffer           buffer for reading response
-  buffsize         size of buffer
+  addrlist          the complete address list
+  include_affixes   TRUE if affixes include in RCPT
+  sync_addr         ptr to the ptr of the one to start scanning at (updated)
+  host              the host we are connected to
+  count             the number of responses to read
+  address_retry_
+    include_sender  true if 4xx retry is to include the sender it its key
+  pending_MAIL      true if the first response is for MAIL
+  pending_DATA      0 if last command sent was not DATA
+                   +1 if previously had a good recipient
+                   -1 if not previously had a good recipient
+  inblock           incoming SMTP block
+  timeout           timeout value
+  buffer            buffer for reading response
+  buffsize          size of buffer
 
 Returns:      3 if at least one address had 2xx and one had 5xx
               2 if at least one address had 5xx but none had 2xx
@@ -580,7 +596,8 @@ Returns:      3 if at least one address had 2xx and one had 5xx
 
 static int
 sync_responses(address_item *addrlist, BOOL include_affixes,
-  address_item **sync_addr, host_item *host, int count, BOOL pending_MAIL,
+  address_item **sync_addr, host_item *host, int count,
+  BOOL address_retry_include_sender, BOOL pending_MAIL,
   int pending_DATA, smtp_inblock *inblock, int timeout, uschar *buffer,
   int buffsize)
 {
@@ -705,10 +722,17 @@ while (count-- > 0)
 
       update_waiting = FALSE;
 
-      /* Add a retry item for the address so that it doesn't get tried
-      again too soon. */
+      /* Add a retry item for the address so that it doesn't get tried again
+      too soon. If address_retry_include_sender is true, add the sender address
+      to the retry key. */
 
-      retry_add_item(addr, addr->address_retry_key, 0);
+      if (address_retry_include_sender)
+        {
+        uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
+          sender_address);
+        retry_add_item(addr, altkey, 0);
+        }
+      else retry_add_item(addr, addr->address_retry_key, 0);
       }
     }
   }       /* Loop for next RCPT response */
@@ -824,7 +848,7 @@ smtp_outblock outblock;
 int max_rcpt = tblock->max_addresses;
 uschar *igquotstr = US"";
 uschar *local_authenticated_sender = authenticated_sender;
-uschar *helo_data;
+uschar *helo_data = NULL;
 uschar *message = NULL;
 uschar new_message_id[MESSAGE_ID_LENGTH + 1];
 uschar *p;
@@ -853,17 +877,6 @@ outblock.ptr = outbuffer;
 outblock.cmd_count = 0;
 outblock.authenticating = FALSE;
 
-/* Expand the greeting message */
-
-helo_data = expand_string(ob->helo_data);
-if (helo_data == NULL)
-  {
-  uschar *message = string_sprintf("failed to expand helo_data: %s",
-    expand_string_message);
-  set_errno(addrlist, 0, message, DEFER, FALSE);
-  return ERROR;
-  }
-
 /* If an authenticated_sender override has been specified for this transport
 instance, expand it. If the expansion is forced to fail, and there was already
 an authenticated_sender for this message, the original value will be used.
@@ -903,6 +916,12 @@ if (continue_hostname == NULL)
     return DEFER;
     }
 
+  /* Expand the greeting message while waiting for the initial response. (Makes
+  sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is
+  delayed till here so that $sending_interface and $sending_port are set. */
+
+  helo_data = expand_string(ob->helo_data);
+
   /* The first thing is to wait for an initial OK response. The dreaded "goto"
   is nevertheless a reasonably clean way of programming this kind of logic,
   where you want to escape on any error. */
@@ -910,6 +929,18 @@ if (continue_hostname == NULL)
   if (!smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
     ob->command_timeout)) goto RESPONSE_FAILED;
 
+  /* Now check if the helo_data expansion went well, and sign off cleanly if it
+  didn't. */
+
+  if (helo_data == NULL)
+    {
+    uschar *message = string_sprintf("failed to expand helo_data: %s",
+      expand_string_message);
+    set_errno(addrlist, 0, message, DEFER, FALSE);
+    yield = DEFER;
+    goto SEND_QUIT;
+    }
+
 /** Debugging without sending a message
 addrlist->transport_return = DEFER;
 goto SEND_QUIT;
@@ -1040,13 +1071,18 @@ if (tls_offered && !suppress_tls &&
 
   else
     {
-    int rc = tls_client_start(inblock.sock, host, addrlist,
+    int rc = tls_client_start(inblock.sock,
+      host,
+      addrlist,
       NULL,                    /* No DH param */
       ob->tls_certificate,
       ob->tls_privatekey,
       ob->tls_verify_certificates,
       ob->tls_crl,
       ob->tls_require_ciphers,
+      ob->gnutls_require_mac,
+      ob->gnutls_require_kx,
+      ob->gnutls_require_proto,
       ob->command_timeout);
 
     /* TLS negotiation failed; give an error. From outside, this function may
@@ -1074,10 +1110,27 @@ if (tls_offered && !suppress_tls &&
     }
   }
 
-/* If we started TLS, redo the EHLO/LHLO exchange over the secure channel. */
+/* If we started TLS, redo the EHLO/LHLO exchange over the secure channel. If
+helo_data is null, we are dealing with a connection that was passed from
+another process, and so we won't have expanded helo_data above. We have to
+expand it here. $sending_ip_address and $sending_port are set up right at the
+start of the Exim process (in exim.c). */
 
 if (tls_active >= 0)
   {
+  if (helo_data == NULL)
+    {
+    helo_data = expand_string(ob->helo_data);
+    if (helo_data == NULL)
+      {
+      uschar *message = string_sprintf("failed to expand helo_data: %s",
+        expand_string_message);
+      set_errno(addrlist, 0, message, DEFER, FALSE);
+      yield = DEFER;
+      goto SEND_QUIT;
+      }
+    }
+
   if (smtp_write_command(&outblock, FALSE, "%s %s\r\n", lmtp? "LHLO" : "EHLO",
         helo_data) < 0)
     goto SEND_FAILED;
@@ -1404,8 +1457,9 @@ for (addr = first_addr;
   if (count > 0)
     {
     switch(sync_responses(first_addr, tblock->rcpt_include_affixes,
-             &sync_addr, host, count, pending_MAIL, 0, &inblock,
-             ob->command_timeout, buffer, sizeof(buffer)))
+             &sync_addr, host, count, ob->address_retry_include_sender,
+             pending_MAIL, 0, &inblock, ob->command_timeout, buffer,
+             sizeof(buffer)))
       {
       case 3: ok = TRUE;                   /* 2xx & 5xx => OK & progress made */
       case 2: completed_address = TRUE;    /* 5xx (only) => progress made */
@@ -1453,8 +1507,8 @@ if (ok || (smtp_use_pipelining && !mua_wrapper))
   int count = smtp_write_command(&outblock, FALSE, "DATA\r\n");
   if (count < 0) goto SEND_FAILED;
   switch(sync_responses(first_addr, tblock->rcpt_include_affixes, &sync_addr,
-           host, count, pending_MAIL, ok? +1 : -1, &inblock,
-           ob->command_timeout, buffer, sizeof(buffer)))
+           host, count, ob->address_retry_include_sender, pending_MAIL,
+           ok? +1 : -1, &inblock, ob->command_timeout, buffer, sizeof(buffer)))
     {
     case 3: ok = TRUE;                   /* 2xx & 5xx => OK & progress made */
     case 2: completed_address = TRUE;    /* 5xx (only) => progress made */
@@ -1660,7 +1714,7 @@ if (!ok) ok = TRUE; else
 
     /* Ensure the journal file is pushed out to disk. */
 
-    if (fsync(journal_fd) < 0)
+    if (EXIMfsync(journal_fd) < 0)
       log_write(0, LOG_MAIN|LOG_PANIC, "failed to fsync journal: %s",
         strerror(errno));
     }