Call initgroups() when dropping privilege, in order that Exim runs with
[exim.git] / src / src / smtp_in.c
index 00d0fb25934a56f09f99ec608c125b4f76deef6c..99ac3fb1a3f402f866aa47163ef01c2cf510e2bf 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/smtp_in.c,v 1.28 2005/12/14 10:00:05 ph10 Exp $ */
+/* $Cambridge: exim/src/src/smtp_in.c,v 1.38 2006/04/19 10:58:21 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 for handling an incoming SMTP call. */
@@ -338,8 +338,13 @@ va_list ap;
 
 DEBUG(D_receive)
   {
+  uschar *cr, *end;
   va_start(ap, format);
   (void) string_vformat(big_buffer, big_buffer_size, format, ap);
+  va_end(ap);
+  end = big_buffer + Ustrlen(big_buffer);
+  while ((cr = Ustrchr(big_buffer, '\r')) != NULL)   /* lose CRs */
+    memmove(cr, cr + 1, (end--) - cr);
   debug_printf("SMTP>> %s", big_buffer);
   }
 
@@ -626,6 +631,8 @@ for (;;)
 
 /* This function is called when logging information about an SMTP connection.
 It sets up appropriate source information, depending on the type of connection.
+If sender_fullhost is NULL, we are at a very early stage of the connection;
+just use the IP address.
 
 Argument:    none
 Returns:     a string describing the connection
@@ -634,21 +641,24 @@ Returns:     a string describing the connection
 uschar *
 smtp_get_connection_info(void)
 {
+uschar *hostname = (sender_fullhost == NULL)?
+  sender_host_address : sender_fullhost;
+
 if (host_checking)
-  return string_sprintf("SMTP connection from %s", sender_fullhost);
+  return string_sprintf("SMTP connection from %s", hostname);
 
 if (sender_host_unknown || sender_host_notsocket)
   return string_sprintf("SMTP connection from %s", sender_ident);
 
 if (is_inetd)
-  return string_sprintf("SMTP connection from %s (via inetd)", sender_fullhost);
+  return string_sprintf("SMTP connection from %s (via inetd)", hostname);
 
 if ((log_extra_selector & LX_incoming_interface) != 0 &&
      interface_address != NULL)
-  return string_sprintf("SMTP connection from %s I=[%s]:%d", sender_fullhost,
+  return string_sprintf("SMTP connection from %s I=[%s]:%d", hostname,
     interface_address, interface_port);
 
-return string_sprintf("SMTP connection from %s", sender_fullhost);
+return string_sprintf("SMTP connection from %s", hostname);
 }
 
 
@@ -803,9 +813,10 @@ rcpt_count = rcpt_defer_count = rcpt_fail_count =
   raw_recipients_count = recipients_count = recipients_list_max = 0;
 message_linecount = 0;
 message_size = -1;
-acl_warn_headers = NULL;
+acl_added_headers = NULL;
 queue_only_policy = FALSE;
 deliver_freeze = FALSE;                              /* Can be set by ACL */
+freeze_tell = freeze_tell_config;                    /* Can be set by ACL */
 fake_response = OK;                                  /* Can be set by ACL */
 #ifdef WITH_CONTENT_SCAN
 no_mbox_unspool = FALSE;                             /* Can be set by ACL */
@@ -1448,19 +1459,40 @@ if (!sender_host_unknown)
     return FALSE;
     }
 
-  /* Test with TCP Wrappers if so configured */
+  /* Test with TCP Wrappers if so configured. There is a problem in that
+  hosts_ctl() returns 0 (deny) under a number of system failure circumstances,
+  such as disks dying. In these cases, it is desirable to reject with a 4xx
+  error instead of a 5xx error. There isn't a "right" way to detect such
+  problems. The following kludge is used: errno is zeroed before calling
+  hosts_ctl(). If the result is "reject", a 5xx error is given only if the
+  value of errno is 0 or ENOENT (which happens if /etc/hosts.{allow,deny} does
+  not exist). */
 
   #ifdef USE_TCP_WRAPPERS
+  errno = 0;
   if (!hosts_ctl("exim",
          (sender_host_name == NULL)? STRING_UNKNOWN : CS sender_host_name,
          (sender_host_address == NULL)? STRING_UNKNOWN : CS sender_host_address,
          (sender_ident == NULL)? STRING_UNKNOWN : CS sender_ident))
     {
-    HDEBUG(D_receive) debug_printf("tcp wrappers rejection\n");
-    log_write(L_connection_reject,
-              LOG_MAIN|LOG_REJECT, "refused connection from %s "
-              "(tcp wrappers)", host_and_ident(FALSE));
-    smtp_printf("554 SMTP service not available\r\n");
+    if (errno == 0 || errno == ENOENT)
+      {
+      HDEBUG(D_receive) debug_printf("tcp wrappers rejection\n");
+      log_write(L_connection_reject,
+                LOG_MAIN|LOG_REJECT, "refused connection from %s "
+                "(tcp wrappers)", host_and_ident(FALSE));
+      smtp_printf("554 SMTP service not available\r\n");
+      }
+    else
+      {
+      int save_errno = errno;
+      HDEBUG(D_receive) debug_printf("tcp wrappers rejected with unexpected "
+        "errno value %d\n", save_errno);
+      log_write(L_connection_reject,
+                LOG_MAIN|LOG_REJECT, "temporarily refused connection from %s "
+                "(tcp wrappers errno=%d)", host_and_ident(FALSE), save_errno);
+      smtp_printf("451 Temporary local problem - please try later\r\n");
+      }
     return FALSE;
     }
   #endif
@@ -1839,19 +1871,21 @@ if (where == ACL_WHERE_RCPT || where == ACL_WHERE_DATA || where == ACL_WHERE_MIM
 
 /* If there's been a sender verification failure with a specific message, and
 we have not sent a response about it yet, do so now, as a preliminary line for
-failures, but not defers. However, log it in both cases. */
+failures, but not defers. However, always log it for defer, and log it for fail
+unless the sender_verify_fail log selector has been turned off. */
 
 if (sender_verified_failed != NULL &&
     !testflag(sender_verified_failed, af_sverify_told))
   {
   setflag(sender_verified_failed, af_sverify_told);
 
-  log_write(0, LOG_MAIN|LOG_REJECT, "%s sender verify %s for <%s>%s",
-    host_and_ident(TRUE),
-    ((sender_verified_failed->special_action & 255) == DEFER)? "defer" : "fail",
-    sender_verified_failed->address,
-    (sender_verified_failed->message == NULL)? US"" :
-    string_sprintf(": %s", sender_verified_failed->message));
+  if (rc != FAIL || (log_extra_selector & LX_sender_verify_fail) != 0)
+    log_write(0, LOG_MAIN|LOG_REJECT, "%s sender verify %s for <%s>%s",
+      host_and_ident(TRUE),
+      ((sender_verified_failed->special_action & 255) == DEFER)? "defer":"fail",
+      sender_verified_failed->address,
+      (sender_verified_failed->message == NULL)? US"" :
+      string_sprintf(": %s", sender_verified_failed->message));
 
   if (rc == FAIL && sender_verified_failed->user_message != NULL)
     smtp_respond(code, FALSE, string_sprintf(
@@ -2133,16 +2167,20 @@ while (done <= 0)
   pid_t pid;
   int start, end, sender_domain, recipient_domain;
   int ptr, size, rc;
-  int c;
+  int c, i;
   auth_instance *au;
 
   switch(smtp_read_command(TRUE))
     {
     /* The AUTH command is not permitted to occur inside a transaction, and may
-    occur successfully only once per connection, and then only when we've
-    advertised it. Actually, that isn't quite true. When TLS is started, all
-    previous information about a connection must be discarded, so a new AUTH is
-    permitted at that time.
+    occur successfully only once per connection. Actually, that isn't quite
+    true. When TLS is started, all previous information about a connection must
+    be discarded, so a new AUTH is permitted at that time.
+
+    AUTH may only be used when it has been advertised. However, it seems that
+    there are clients that send AUTH when it hasn't been advertised, some of
+    them even doing this after HELO. And there are MTAs that accept this. Sigh.
+    So there's a get-out that allows this to happen.
 
     AUTH is initially labelled as a "nonmail command" so that one occurrence
     doesn't get counted. We change the label here so that multiple failing
@@ -2152,7 +2190,7 @@ while (done <= 0)
     authentication_failed = TRUE;
     cmd_list[CMD_LIST_AUTH].is_mail_cmd = FALSE;
 
-    if (!auth_advertised)
+    if (!auth_advertised && !allow_auth_unadvertised)
       {
       done = synprot_error(L_smtp_protocol_error, 503, NULL,
         US"AUTH command used when not advertised");
@@ -2207,12 +2245,13 @@ while (done <= 0)
       }
 
     /* Search for an authentication mechanism which is configured for use
-    as a server and which has been advertised. */
+    as a server and which has been advertised (unless, sigh, allow_auth_
+    unadvertised is set). */
 
     for (au = auths; au != NULL; au = au->next)
       {
       if (strcmpic(s, au->public_name) == 0 && au->server &&
-          au->advertised) break;
+          (au->advertised || allow_auth_unadvertised)) break;
       }
 
     if (au == NULL)
@@ -2222,20 +2261,26 @@ while (done <= 0)
       break;
       }
 
-    /* Run the checking code, passing the remainder of the command
-    line as data. Initialize $0 empty. The authenticator may set up
-    other numeric variables. Afterwards, have a go at expanding the set_id
-    string, even if authentication failed - for bad passwords it can be useful
-    to log the userid. On success, require set_id to expand and exist, and
-    put it in authenticated_id. Save this in permanent store, as the working
-    store gets reset at HELO, RSET, etc. */
+    /* Run the checking code, passing the remainder of the command line as
+    data. Initials the $auth<n> variables as empty. Initialize $0 empty and set
+    it as the only set numerical variable. The authenticator may set $auth<n>
+    and also set other numeric variables. The $auth<n> variables are preferred
+    nowadays; the numerical variables remain for backwards compatibility.
 
+    Afterwards, have a go at expanding the set_id string, even if
+    authentication failed - for bad passwords it can be useful to log the
+    userid. On success, require set_id to expand and exist, and put it in
+    authenticated_id. Save this in permanent store, as the working store gets
+    reset at HELO, RSET, etc. */
+
+    for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
     expand_nmax = 0;
     expand_nlength[0] = 0;   /* $0 contains nothing */
 
     c = (au->info->servercode)(au, smtp_cmd_argument);
     if (au->set_id != NULL) set_id = expand_string(au->set_id);
     expand_nmax = -1;        /* Reset numeric variables */
+    for (i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;   /* Reset $auth<n> */
 
     /* The value of authenticated_id is stored in the spool file and printed in
     log lines. It must not contain binary zeros or newline characters. In
@@ -2624,7 +2669,13 @@ while (done <= 0)
     #endif
 
     (void)fwrite(s, 1, ptr, smtp_out);
-    DEBUG(D_receive) debug_printf("SMTP>> %s", s);
+    DEBUG(D_receive)
+      {
+      uschar *cr;
+      while ((cr = Ustrchr(s, '\r')) != NULL)   /* lose CRs */
+        memmove(cr, cr + 1, (ptr--) - (cr - s));
+      debug_printf("SMTP>> %s", s);
+      }
     helo_seen = TRUE;
     break;   /* HELO/EHLO */
 
@@ -3178,14 +3229,14 @@ while (done <= 0)
           break;
 
           case DEFER:
-          s = (addr->message != NULL)?
-            string_sprintf("451 <%s> %s", address, addr->message) :
+          s = (addr->user_message != NULL)?
+            string_sprintf("451 <%s> %s", address, addr->user_message) :
             string_sprintf("451 Cannot resolve <%s> at this time", address);
           break;
 
           case FAIL:
-          s = (addr->message != NULL)?
-            string_sprintf("550 <%s> %s", address, addr->message) :
+          s = (addr->user_message != NULL)?
+            string_sprintf("550 <%s> %s", address, addr->user_message) :
             string_sprintf("550 <%s> is not deliverable", address);
           log_write(0, LOG_MAIN, "VRFY failed for %s %s",
             smtp_cmd_argument, host_and_ident(TRUE));