Add support for the IGNOREQUOTA extension to LMTP, both to the lmtp
[exim.git] / src / src / transports / smtp.c
index edcdc409d799fe0482ae8059c572d3e23fdf0aec..42179898a972d4d6554c1c7e3e18bd57c4536dfc 100644 (file)
@@ -1,4 +1,4 @@
-/* $Cambridge: exim/src/src/transports/smtp.c,v 1.8 2005/03/22 15:45:35 ph10 Exp $ */
+/* $Cambridge: exim/src/src/transports/smtp.c,v 1.15 2005/08/02 11:22:24 ph10 Exp $ */
 
 /*************************************************
 *     Exim - an Internet mail transport agent    *
@@ -93,6 +93,8 @@ optionlist smtp_transport_options[] = {
       (void *)offsetof(smtp_transport_options_block, interface) },
   { "keepalive",            opt_bool,
       (void *)offsetof(smtp_transport_options_block, keepalive) },
+  { "lmtp_ignore_quota",    opt_bool,
+      (void *)offsetof(smtp_transport_options_block, lmtp_ignore_quota) },
   { "max_rcpt",             opt_int | opt_public,
       (void *)offsetof(transport_instance, max_addresses) },
   { "multi_domain",         opt_bool | opt_public,
@@ -163,6 +165,7 @@ smtp_transport_options_block smtp_transport_option_defaults = {
   FALSE,               /* hosts_override */
   FALSE,               /* hosts_randomize */
   TRUE,                /* keepalive */
+  FALSE,               /* lmtp_ignore_quota */
   TRUE                 /* retry_include_ip_address */
   #ifdef SUPPORT_TLS
  ,NULL,                /* tls_certificate */
@@ -313,10 +316,11 @@ host_build_hostlist(&(ob->fallback_hostlist), ob->fallback_hosts, FALSE);
 status means that an address is not currently being processed.
 
 Arguments:
-  addrlist     points to a chain of addresses
-  errno_value  to put in each address's errno field
-  msg          to put in each address's message field
-  rc           to put in each address's transport_return field
+  addrlist       points to a chain of addresses
+  errno_value    to put in each address's errno field
+  msg            to put in each address's message field
+  rc             to put in each address's transport_return field
+  pass_message   if TRUE, set the "pass message" flag in the address
 
 If errno_value has the special value ERRNO_CONNECTTIMEOUT, ETIMEDOUT is put in
 the errno field, and RTEF_CTOUT is ORed into the more_errno field, to indicate
@@ -326,7 +330,8 @@ Returns:       nothing
 */
 
 static
-void set_errno(address_item *addrlist, int errno_value, uschar *msg, int rc)
+void set_errno(address_item *addrlist, int errno_value, uschar *msg, int rc,
+  BOOL pass_message)
 {
 address_item *addr;
 int orvalue = 0;
@@ -340,7 +345,11 @@ for (addr = addrlist; addr != NULL; addr = addr->next)
   if (addr->transport_return < PENDING) continue;
   addr->basic_errno = errno_value;
   addr->more_errno |= orvalue;
-  if (msg != NULL) addr->message = msg;
+  if (msg != NULL)
+    {
+    addr->message = msg;
+    if (pass_message) setflag(addr, af_pass_message);
+    }
   addr->transport_return = rc;
   }
 }
@@ -358,18 +367,19 @@ the yield variable. If no response was actually read, a suitable digit is
 chosen.
 
 Arguments:
-  host         the current host, to get its name for messages
-  errno_value  pointer to the errno value
-  more_errno   from the top address for use with ERRNO_FILTER_FAIL
-  buffer       the SMTP response buffer
-  yield        where to put a one-digit SMTP response code
-  message      where to put an errror message
-
-Returns:       TRUE if an SMTP "QUIT" command should be sent, else FALSE
+  host           the current host, to get its name for messages
+  errno_value    pointer to the errno value
+  more_errno     from the top address for use with ERRNO_FILTER_FAIL
+  buffer         the SMTP response buffer
+  yield          where to put a one-digit SMTP response code
+  message        where to put an errror message
+  pass_message   set TRUE if message is an SMTP response
+
+Returns:         TRUE if an SMTP "QUIT" command should be sent, else FALSE
 */
 
 static BOOL check_response(host_item *host, int *errno_value, int more_errno,
-  uschar *buffer, int *yield, uschar **message)
+  uschar *buffer, int *yield, uschar **message, BOOL *pass_message)
 {
 uschar *pl = US"";
 
@@ -444,8 +454,9 @@ if (*errno_value == ERRNO_WRITEINCOMPLETE)
 if (buffer[0] != 0)
   {
   uschar *s = string_printing(buffer);
-  *message = US string_sprintf("SMTP error from remote mailer after %s%s: "
+  *message = US string_sprintf("SMTP error from remote mail server after %s%s: "
     "host %s [%s]: %s", pl, smtp_command, host->name, host->address, s);
+  *pass_message = TRUE;
   *yield = buffer[0];
   return TRUE;
   }
@@ -623,7 +634,7 @@ while (count-- > 0)
     uschar *message = string_sprintf("SMTP timeout while connected to %s [%s] "
       "after RCPT TO:<%s>", host->name, host->address,
       transport_rcpt_address(addr, include_affixes));
-    set_errno(addrlist, save_errno, message, DEFER);
+    set_errno(addrlist, save_errno, message, DEFER, FALSE);
     retry_add_item(addr, addr->address_retry_key, 0);
     host->update_waiting = FALSE;
     return -1;
@@ -646,9 +657,10 @@ while (count-- > 0)
   else
     {
     addr->message =
-      string_sprintf("SMTP error from remote mailer after RCPT TO:<%s>: "
+      string_sprintf("SMTP error from remote mail server after RCPT TO:<%s>: "
         "host %s [%s]: %s", transport_rcpt_address(addr, include_affixes),
         host->name, host->address, string_printing(buffer));
+    setflag(addr, af_pass_message);
     deliver_msglog("%s %s\n", tod_stamp(tod_log), addr->message);
 
     /* The response was 5xx */
@@ -699,8 +711,9 @@ if (pending_DATA != 0 &&
   {
   int code;
   uschar *msg;
+  BOOL pass_message;
   if (pending_DATA > 0 || (yield & 1) != 0) return -3;
-  (void)check_response(host, &errno, 0, buffer, &code, &msg);
+  (void)check_response(host, &errno, 0, buffer, &code, &msg, &pass_message);
   DEBUG(D_transport) debug_printf("%s\nerror for DATA ignored: pipelining "
     "is in use and there were no good recipients\n", msg);
   }
@@ -782,9 +795,11 @@ BOOL setting_up = TRUE;
 BOOL completed_address = FALSE;
 BOOL esmtp = TRUE;
 BOOL pending_MAIL;
+BOOL pass_message = FALSE;
 smtp_inblock inblock;
 smtp_outblock outblock;
 int max_rcpt = tblock->max_addresses;
+uschar *igquotstr = US"";
 uschar *local_authenticated_sender = authenticated_sender;
 uschar *helo_data;
 uschar *message = NULL;
@@ -822,7 +837,7 @@ if (helo_data == NULL)
   {
   uschar *message = string_sprintf("failed to expand helo_data: %s",
     expand_string_message);
-  set_errno(addrlist, 0, message, DEFER);
+  set_errno(addrlist, 0, message, DEFER, FALSE);
   return ERROR;
   }
 
@@ -842,7 +857,7 @@ if (ob->authenticated_sender != NULL)
       {
       uschar *message = string_sprintf("failed to expand "
         "authenticated_sender: %s", expand_string_message);
-      set_errno(addrlist, 0, message, DEFER);
+      set_errno(addrlist, 0, message, DEFER, FALSE);
       return ERROR;
       }
     }
@@ -861,7 +876,7 @@ if (continue_hostname == NULL)
   if (inblock.sock < 0)
     {
     set_errno(addrlist, (errno == ETIMEDOUT)? ERRNO_CONNECTTIMEOUT : errno,
-      NULL, DEFER);
+      NULL, DEFER, FALSE);
     return DEFER;
     }
 
@@ -936,6 +951,13 @@ goto SEND_QUIT;
       ob->command_timeout)) goto RESPONSE_FAILED;
     }
 
+  /* Set IGNOREQUOTA if the response to LHLO specifies support and the
+  lmtp_ignore_quota option was set. */
+
+  igquotstr = (lmtp && ob->lmtp_ignore_quota &&
+    pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer, Ustrlen(CS buffer), 0,
+      PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US"";
+
   /* Set tls_offered if the response to EHLO specifies support for STARTTLS. */
 
   #ifdef SUPPORT_TLS
@@ -1020,8 +1042,11 @@ if (tls_offered && !suppress_tls &&
 
     for (addr = addrlist; addr != NULL; addr = addr->next)
       {
-      addr->cipher = tls_cipher;
-      addr->peerdn = tls_peerdn;
+      if (addr->transport_return == PENDING_DEFER)
+        {
+        addr->cipher = tls_cipher;
+        addr->peerdn = tls_peerdn;
+        }
       }
     }
   }
@@ -1067,6 +1092,13 @@ if (continue_hostname == NULL
   int require_auth;
   uschar *fail_reason = US"server did not advertise AUTH support";
 
+  /* Set for IGNOREQUOTA if the response to LHLO specifies support and the
+  lmtp_ignore_quota option was set. */
+
+  igquotstr = (lmtp && ob->lmtp_ignore_quota &&
+    pcre_exec(regex_IGNOREQUOTA, NULL, CS buffer, Ustrlen(CS buffer), 0,
+      PCRE_EOPT, NULL, 0) >= 0)? US" IGNOREQUOTA" : US"";
+
   /* If the response to EHLO specified support for the SIZE parameter, note
   this, provided size_addition is non-negative. */
 
@@ -1183,7 +1215,7 @@ if (continue_hostname == NULL
 
             case ERROR:
             yield = ERROR;
-            set_errno(addrlist, 0, string_copy(buffer), DEFER);
+            set_errno(addrlist, 0, string_copy(buffer), DEFER, FALSE);
             goto SEND_QUIT;
             }
 
@@ -1199,7 +1231,8 @@ if (continue_hostname == NULL
     {
     yield = DEFER;
     set_errno(addrlist, ERRNO_AUTHFAIL,
-      string_sprintf("authentication required but %s", fail_reason), DEFER);
+      string_sprintf("authentication required but %s", fail_reason), DEFER,
+      FALSE);
     goto SEND_QUIT;
     }
   }
@@ -1225,7 +1258,8 @@ if (tblock->filter_command != NULL)
 
   if (!rc)
     {
-    set_errno(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER);
+    set_errno(addrlist->next, addrlist->basic_errno, addrlist->message, DEFER,
+      FALSE);
     yield = ERROR;
     goto SEND_QUIT;
     }
@@ -1327,8 +1361,8 @@ for (addr = first_addr;
   yield as OK, because this error can often mean that there is a problem with
   just one address, so we don't want to delay the host. */
 
-  count = smtp_write_command(&outblock, no_flush, "RCPT TO:<%s>\r\n",
-    transport_rcpt_address(addr, tblock->rcpt_include_affixes));
+  count = smtp_write_command(&outblock, no_flush, "RCPT TO:<%s>%s\r\n",
+    transport_rcpt_address(addr, tblock->rcpt_include_affixes), igquotstr);
   if (count < 0) goto SEND_FAILED;
   if (count > 0)
     {
@@ -1365,7 +1399,8 @@ if (mua_wrapper)
     }
   if (badaddr != NULL)
     {
-    set_errno(addrlist, 0, badaddr->message, FAIL);
+    set_errno(addrlist, 0, badaddr->message, FAIL,
+      testflag(badaddr, af_pass_message));
     ok = FALSE;
     }
   }
@@ -1594,7 +1629,7 @@ if (!ok)
   save_errno = errno;
   message = NULL;
   send_quit = check_response(host, &save_errno, addrlist->more_errno,
-    buffer, &code, &message);
+    buffer, &code, &message, &pass_message);
   goto FAILED;
 
   SEND_FAILED:
@@ -1629,11 +1664,11 @@ if (!ok)
     {
     if (code == '5')
       {
-      set_errno(addrlist, save_errno, message, FAIL);
+      set_errno(addrlist, save_errno, message, FAIL, pass_message);
       }
     else
       {
-      set_errno(addrlist, save_errno, message, DEFER);
+      set_errno(addrlist, save_errno, message, DEFER, pass_message);
       yield = DEFER;
       }
     }
@@ -1659,7 +1694,7 @@ if (!ok)
     {
     yield = (save_errno == ERRNO_CHHEADER_FAIL ||
              save_errno == ERRNO_FILTER_FAIL)? ERROR : DEFER;
-    set_errno(addrlist, save_errno, message, DEFER);
+    set_errno(addrlist, save_errno, message, DEFER, pass_message);
     }
 
   /* Otherwise we have a message-specific error response from the remote
@@ -1680,7 +1715,8 @@ if (!ok)
     {
     if (mua_wrapper) code = '5';  /* Force hard failure in wrapper mode */
 
-    set_errno(addrlist, save_errno, message, (code == '5')? FAIL : DEFER);
+    set_errno(addrlist, save_errno, message, (code == '5')? FAIL : DEFER,
+      pass_message);
 
     /* If there's an errno, the message contains just the identity of
     the host. */
@@ -1744,6 +1780,7 @@ if (completed_address && ok && send_quit)
         ))
     {
     uschar *msg;
+    BOOL pass_message;
 
     if (send_rset)
       {
@@ -1757,7 +1794,8 @@ if (completed_address && ok && send_quit)
                   ob->command_timeout)))
         {
         int code;
-        send_quit = check_response(host, &errno, 0, buffer, &code, &msg);
+        send_quit = check_response(host, &errno, 0, buffer, &code, &msg,
+          &pass_message);
         if (!send_quit)
           {
           DEBUG(D_transport) debug_printf("%s\n", msg);
@@ -1803,7 +1841,7 @@ if (completed_address && ok && send_quit)
 
     /* If RSET failed and there are addresses left, they get deferred. */
 
-    else set_errno(first_addr, errno, msg, DEFER);
+    else set_errno(first_addr, errno, msg, DEFER, FALSE);
     }
   }
 
@@ -1846,7 +1884,7 @@ writing RSET might have failed, or there may be other addresses whose hosts are
 specified in the transports, and therefore not visible at top level, in which
 case continue_more won't get set. */
 
-close(inblock.sock);
+(void)close(inblock.sock);
 continue_transport = NULL;
 continue_hostname = NULL;
 return yield;
@@ -1899,7 +1937,7 @@ outblock.authenticating = FALSE;
 (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n");
 (void)smtp_read_response(&inblock, buffer, sizeof(buffer), '2',
   ob->command_timeout);
-close(inblock.sock);
+(void)close(inblock.sock);
 }
 
 
@@ -2172,6 +2210,12 @@ for (cutoff_retry = 0; expired &&
     uschar *retry_message_key = NULL;
     uschar *serialize_key = NULL;
 
+    /* Default next host is next host. :-) But this can vary if the
+    hosts_max_try limit is hit (see below). It may also be reset if a host
+    address is looked up here (in case the host was multihomed). */
+
+    nexthost = host->next;
+
     /* Set the flag requesting that this host be added to the waiting
     database if the delivery fails temporarily or if we are running with
     queue_smtp or a 2-stage queue run. This gets unset for certain
@@ -2278,10 +2322,8 @@ for (cutoff_retry = 0; expired &&
       continue;      /* With next host */
       }
 
-    /* The default next host is the next host. :-) But this can vary if the
-    hosts_max_try limit is hit (see below). NOTE: we cannot put this setting
-    earlier than this, because a multihomed host whose addresses are not looked
-    up till just above will add to the host list. */
+    /* Reset the default next host in case a multihomed host whose addresses
+    are not looked up till just above added to the host list. */
 
     nexthost = host->next;
 
@@ -2293,8 +2335,8 @@ for (cutoff_retry = 0; expired &&
     doing a two-stage queue run, don't do this if forcing. */
 
     if ((!deliver_force || queue_2stage) && (queue_smtp ||
-        match_isinlist(addrlist->domain, &queue_smtp_domains, 0, NULL, NULL,
-          MCL_DOMAIN, TRUE, NULL) == OK))
+        match_isinlist(addrlist->domain, &queue_smtp_domains, 0,
+          &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK))
       {
       expired = FALSE;
       for (addr = addrlist; addr != NULL; addr = addr->next)
@@ -2442,7 +2484,7 @@ for (cutoff_retry = 0; expired &&
     if (dont_deliver)
       {
       host_item *host2;
-      set_errno(addrlist, 0, NULL, OK);
+      set_errno(addrlist, 0, NULL, OK, FALSE);
       for (addr = addrlist; addr != NULL; addr = addr->next)
         {
         addr->host_used = host;
@@ -2764,6 +2806,7 @@ for (addr = addrlist; addr != NULL; addr = addr->next)
       }
     else if (expired)
       {
+      setflag(addr, af_pass_message);   /* This is not a security risk */
       addr->message = (ob->delay_after_cutoff)?
         US"retry time not reached for any host after a long failure period" :
         US"all hosts have been failing for a long time and were last tried "