Apply Jakob Hirsch's patch for arbitrary ACL variable names, tidied up
[exim.git] / src / src / exim.c
index 0ed6c7ae571df48019eb242b21e7bbd8518b6c73..d209b767e4ea62a3f5fe4d26fc853f12a61f9d59 100644 (file)
@@ -1,10 +1,10 @@
-/* $Cambridge: exim/src/src/exim.c,v 1.20 2005/05/31 11:10:50 ph10 Exp $ */
+/* $Cambridge: exim/src/src/exim.c,v 1.43 2006/09/18 14:49:23 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. */
 
 
@@ -375,6 +375,38 @@ va_end(ap);
 
 
 
+/*************************************************
+*   Call fopen() with umask 777 and adjust mode  *
+*************************************************/
+
+/* Exim runs with umask(0) so that files created with open() have the mode that
+is specified in the open() call. However, there are some files, typically in
+the spool directory, that are created with fopen(). They end up world-writeable
+if no precautions are taken. Although the spool directory is not accessible to
+the world, this is an untidiness. So this is a wrapper function for fopen()
+that sorts out the mode of the created file.
+
+Arguments:
+   filename       the file name
+   options        the fopen() options
+   mode           the required mode
+
+Returns:          the fopened FILE or NULL
+*/
+
+FILE *
+modefopen(uschar *filename, char *options, mode_t mode)
+{
+mode_t saved_umask = umask(0777);
+FILE *f = Ufopen(filename, options);
+(void)umask(saved_umask);
+if (f != NULL) (void)fchmod(fileno(f), mode);
+return f;
+}
+
+
+
+
 /*************************************************
 *   Ensure stdin, stdout, and stderr exist       *
 *************************************************/
@@ -406,10 +438,10 @@ for (i = 0; i <= 2; i++)
     if (devnull < 0) devnull = open("/dev/null", O_RDWR);
     if (devnull < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
       string_open_failed(errno, "/dev/null"));
-    if (devnull != i) dup2(devnull, i);
+    if (devnull != i) (void)dup2(devnull, i);
     }
   }
-if (devnull > 2) close(devnull);
+if (devnull > 2) (void)close(devnull);
 }
 
 
@@ -459,19 +491,19 @@ if (smtp_input)
   #ifdef SUPPORT_TLS
   tls_close(FALSE);      /* Shut down the TLS library */
   #endif
-  close(fileno(smtp_in));
-  close(fileno(smtp_out));
+  (void)close(fileno(smtp_in));
+  (void)close(fileno(smtp_out));
   smtp_in = NULL;
   }
 else
   {
-  close(0);                                           /* stdin */
-  if ((debug_selector & D_resolver) == 0)  close(1);  /* stdout */
-  if (debug_selector == 0)                            /* stderr */
+  (void)close(0);                                          /* stdin */
+  if ((debug_selector & D_resolver) == 0) (void)close(1);  /* stdout */
+  if (debug_selector == 0)                                 /* stderr */
     {
     if (!synchronous_delivery)
       {
-      close(2);
+      (void)close(2);
       log_stderr = NULL;
       }
     (void)setsid();
@@ -599,7 +631,7 @@ Returns:    the port, or zero if there isn't one
 static int
 check_port(uschar *address)
 {
-int port = host_extract_port(address);
+int port = host_address_extract_port(address);
 if (string_is_ip_address(address, NULL) == 0)
   {
   fprintf(stderr, "exim abandoned: \"%s\" is not an IP address\n", address);
@@ -663,6 +695,10 @@ The log options are held in two unsigned ints (because there became too many
 for one). The top bit in the table means "put in 2nd selector". This does not
 yet apply to debug options, so the "=" facility sets only the first selector.
 
+The "all" selector, which must be equal to 0xffffffff, is recognized specially.
+It sets all the bits in both selectors. However, there is a facility for then
+unsetting certain bits, because we want to turn off "memory" in the debug case.
+
 A bad value for a debug setting is treated as an unknown option - error message
 to stderr and die. For log settings, which come from the configuration file,
 we write to the log on the way out...
@@ -670,6 +706,8 @@ we write to the log on the way out...
 Arguments:
   selector1      address of the first bit string
   selector2      address of the second bit string, or NULL
+  notall1        bits to exclude from "all" for selector1
+  notall2        bits to exclude from "all" for selector2
   string         the configured string
   options        the table of option names
   count          size of table
@@ -679,8 +717,8 @@ Returns:         nothing on success - bomb out on failure
 */
 
 static void
-decode_bits(unsigned int *selector1, unsigned int *selector2, uschar *string,
-  bit_table *options, int count, uschar *which)
+decode_bits(unsigned int *selector1, unsigned int *selector2, int notall1,
+  int notall2, uschar *string, bit_table *options, int count, uschar *which)
 {
 uschar *errmsg;
 if (string == NULL) return;
@@ -733,14 +771,23 @@ else for(;;)
         unsigned int bit = middle->bit;
         unsigned int *selector;
 
-        /* The value with all bits set means "set all bits in both selectors"
+        /* The value with all bits set means "force all bits in both selectors"
         in the case where two are being handled. However, the top bit in the
-        second selector is never set. */
+        second selector is never set. When setting, some bits can be excluded.
+        */
 
         if (bit == 0xffffffff)
           {
-          *selector1 = adding? bit : 0;
-          if (selector2 != NULL) *selector2 = adding? 0x7fffffff : 0;
+          if (adding)
+            {
+            *selector1 = 0xffffffff ^ notall1;
+            if (selector2 != NULL) *selector2 = 0x7fffffff ^ notall2;
+            }
+          else
+            {
+            *selector1 = 0;
+            if (selector2 != NULL) *selector2 = 0;
+            }
           }
 
         /* Otherwise, the 0x80000000 bit means "this value, without the top
@@ -818,12 +865,18 @@ fprintf(f, "Using tdb\n");
 #endif
 
 fprintf(f, "Support for:");
+#ifdef SUPPORT_CRYPTEQ
+  fprintf(f, " crypteq");
+#endif
 #if HAVE_ICONV
   fprintf(f, " iconv()");
 #endif
 #if HAVE_IPV6
   fprintf(f, " IPv6");
 #endif
+#ifdef HAVE_SETCLASSRESOURCES
+  fprintf(f, " use_setclassresources");
+#endif
 #ifdef SUPPORT_PAM
   fprintf(f, " PAM");
 #endif
@@ -843,6 +896,12 @@ fprintf(f, "Support for:");
   fprintf(f, " OpenSSL");
   #endif
 #endif
+#ifdef SUPPORT_TRANSLATE_IP_ADDRESS
+  fprintf(f, " translate_ip_address");
+#endif
+#ifdef SUPPORT_MOVE_FROZEN_MESSAGES
+  fprintf(f, " move_frozen_messages");
+#endif
 #ifdef WITH_CONTENT_SCAN
   fprintf(f, " Content_Scanning");
 #endif
@@ -903,6 +962,9 @@ fprintf(f, "Lookups:");
 #ifdef LOOKUP_PGSQL
   fprintf(f, " pgsql");
 #endif
+#ifdef LOOKUP_SQLITE
+  fprintf(f, " sqlite");
+#endif
 #ifdef LOOKUP_TESTDB
   fprintf(f, " testdb");
 #endif
@@ -985,6 +1047,8 @@ if (fixed_never_users[0] > 0)
     fprintf(f, "%d:", (unsigned int)fixed_never_users[i]);
   fprintf(f, "%d\n", (unsigned int)fixed_never_users[i]);
   }
+
+fprintf(f, "Size of off_t: %d\n", sizeof(off_t));
 }
 
 
@@ -1223,6 +1287,7 @@ BOOL more = TRUE;
 BOOL one_msg_action = FALSE;
 BOOL queue_only_set = FALSE;
 BOOL receiving_message = TRUE;
+BOOL sender_ident_set = FALSE;
 BOOL unprivileged;
 BOOL removed_privilege = FALSE;
 BOOL verify_address_mode = FALSE;
@@ -1408,10 +1473,17 @@ message_id_external[0] = 'E';
 message_id = message_id_external + 1;
 message_id[0] = 0;
 
-/* Set the umask to zero so that any files that Exim creates are created
-with the modes that it specifies. */
+/* Set the umask to zero so that any files Exim creates using open() are
+created with the modes that it specifies. NOTE: Files created with fopen() have
+a problem, which was not recognized till rather late (February 2006). With this
+umask, such files will be world writeable. (They are all content scanning files
+in the spool directory, which isn't world-accessible, so this is not a
+disaster, but it's untidy.) I don't want to change this overall setting,
+however, because it will interact badly with the open() calls. Instead, there's
+now a function called modefopen() that fiddles with the umask while calling
+fopen(). */
 
-umask(0);
+(void)umask(0);
 
 /* Precompile the regular expression for matching a message id. Keep this in
 step with the code that generates ids in the accept.c module. We need to do
@@ -1421,6 +1493,13 @@ using mac_ismsgid, which uses this. */
 regex_ismsgid =
   regex_must_compile(US"^(?:[^\\W_]{6}-){2}[^\\W_]{2}$", FALSE, TRUE);
 
+/* Precompile the regular expression that is used for matching an SMTP error
+code, possibly extended, at the start of an error message. */
+
+regex_smtp_code =
+  regex_must_compile(US"^\\d\\d\\d\\s(?:\\d\\.\\d\\d?\\d?\\.\\d\\d?\\d?\\s)?",
+    FALSE, TRUE);
+
 /* If the program is called as "mailq" treat it as equivalent to "exim -bp";
 this seems to be a generally accepted convention, since one finds symbolic
 links called "mailq" in standard OS configurations. */
@@ -1920,7 +1999,7 @@ for (i = 1; i < argc; i++)
         argrest++;
         }
       if (*argrest != 0)
-        decode_bits(&selector, NULL, argrest, debug_options,
+        decode_bits(&selector, NULL, D_memory, 0, argrest, debug_options,
           debug_options_count, US"debug");
       debug_selector = selector;
       }
@@ -1972,6 +2051,7 @@ for (i = 1; i < argc; i++)
         { badarg = TRUE; break; }
       }
     originator_name = argrest;
+    sender_name_forced = TRUE;
     break;
 
 
@@ -2441,7 +2521,11 @@ for (i = 1; i < argc; i++)
 
       /* -oMt: Set sender ident */
 
-      else if (Ustrcmp(argrest, "Mt") == 0) sender_ident = argv[++i];
+      else if (Ustrcmp(argrest, "Mt") == 0)
+        {
+        sender_ident_set = TRUE;
+        sender_ident = argv[++i];
+        }
 
       /* Else a bad argument */
 
@@ -3041,7 +3125,7 @@ readconf_main();
 
 /* Handle the decoding of logging options. */
 
-decode_bits(&log_write_selector, &log_extra_selector, log_selector_string,
+decode_bits(&log_write_selector, &log_extra_selector, 0, 0, log_selector_string,
   log_options, log_options_count, US"log");
 
 DEBUG(D_any)
@@ -3282,11 +3366,12 @@ if (((debug_selector & D_any) != 0 || (log_extra_selector & LX_arguments) != 0)
 on this in the code, which always uses fully qualified names, but it's useful
 for core dumps etc. Don't complain if it fails - the spool directory might not
 be generally accessible and calls with the -C option (and others) have lost
-privilege by now. */
+privilege by now. Before the chdir, we try to ensure that the directory exists.
+*/
 
 if (Uchdir(spool_directory) != 0)
   {
-  (void)directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, TRUE);
+  (void)directory_make(spool_directory, US"", SPOOL_DIRECTORY_MODE, FALSE);
   (void)Uchdir(spool_directory);
   }
 
@@ -3298,7 +3383,7 @@ script. */
 
 if (bi_option)
   {
-  fclose(config_file);
+  (void)fclose(config_file);
   if (bi_command != NULL)
     {
     int i = 0;
@@ -3524,7 +3609,9 @@ root privilege above as a result of -C, -D, -be, -bf or -bF, remove it now
 except when starting the daemon or doing some kind of delivery or address
 testing (-bt). These are the only cases when root need to be retained. We run
 as exim for -bv and -bh. However, if deliver_drop_privilege is set, root is
-retained only for starting the daemon. */
+retained only for starting the daemon. We always do the initgroups() in this
+situation (controlled by the TRUE below), in order to be as close as possible
+to the state Exim usually runs in. */
 
 if (!unprivileged &&                      /* originally had root AND */
     !removed_privilege &&                 /* still got root AND      */
@@ -3540,7 +3627,7 @@ if (!unprivileged &&                      /* originally had root AND */
         )
       ))
   {
-  exim_setugid(exim_uid, exim_gid, FALSE, US"privilege not needed");
+  exim_setugid(exim_uid, exim_gid, TRUE, US"privilege not needed");
   }
 
 /* When we are retaining a privileged uid, we still change to the exim gid. */
@@ -3652,11 +3739,13 @@ if (test_retry_arg >= 0)
       return EXIT_FAILURE;
       }
 
-    /* For the rcpt_4xx errors, a value of 255 means "any", and a code > 100 as
-    an error is for matching codes to the decade. Turn them into a real error
-    code, off the decade. */
+    /* For the {MAIL,RCPT,DATA}_4xx errors, a value of 255 means "any", and a
+    code > 100 as an error is for matching codes to the decade. Turn them into
+    a real error code, off the decade. */
 
-    if (basic_errno == ERRNO_RCPT4XX)
+    if (basic_errno == ERRNO_MAIL4XX ||
+        basic_errno == ERRNO_RCPT4XX ||
+        basic_errno == ERRNO_DATA4XX)
       {
       int code = (more_errno >> 8) & 255;
       if (code == 255)
@@ -3890,7 +3979,7 @@ for (i = 0;;)
 
 /* If we cannot get a user login, log the incident and give up, unless the
 configuration specifies something to use. When running in the test harness,
-any setting of unknown_login overrides the actual login name. */
+any setting of unknown_login overrides the actual name. */
 
 if (originator_login == NULL || running_in_test_harness)
   {
@@ -3924,12 +4013,17 @@ DEBUG(D_receive) debug_printf("originator: uid=%d gid=%d login=%s name=%s\n",
 
 /* Run in daemon and/or queue-running mode. The function daemon_go() never
 returns. We leave this till here so that the originator_ fields are available
-for incoming messages via the daemon. */
+for incoming messages via the daemon. The daemon cannot be run in mua_wrapper
+mode. */
 
 if (daemon_listen || queue_interval > 0)
   {
-  if (mua_wrapper) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Daemon cannot be "
-    "run when mua_wrapper is set");
+  if (mua_wrapper)
+    {
+    fprintf(stderr, "Daemon cannot be run when mua_wrapper is set\n");
+    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Daemon cannot be run when "
+      "mua_wrapper is set");
+    }
   daemon_go();
   }
 
@@ -3966,12 +4060,14 @@ if ((sender_address == NULL && !smtp_input) ||
   sender_local = TRUE;
 
   /* A trusted caller can supply authenticated_sender and authenticated_id
-  via -oMas and -oMai and if so, they will already be set. */
+  via -oMas and -oMai and if so, they will already be set. Otherwise, force
+  defaults except when host checking. */
 
-  if (authenticated_sender == NULL)
+  if (authenticated_sender == NULL && !host_checking)
     authenticated_sender = string_sprintf("%s@%s", originator_login,
       qualify_domain_sender);
-  if (authenticated_id == NULL) authenticated_id = originator_login;
+  if (authenticated_id == NULL && !host_checking)
+    authenticated_id = originator_login;
   }
 
 /* Trusted callers are always permitted to specify the sender address.
@@ -4136,20 +4232,24 @@ if (raw_active_hostname != NULL)
   }
 
 /* Handle host checking: this facility mocks up an incoming SMTP call from a
-given IP address so that the blocking and relay configuration can be tested. An
-RFC 1413 call is made only if we are running in the test harness and an
-incoming interface and both ports are specified, because there is no TCP/IP
-call to find the ident for. */
+given IP address so that the blocking and relay configuration can be tested.
+Unless a sender_ident was set by -oMt, we discard it (the default is the
+caller's login name). An RFC 1413 call is made only if we are running in the
+test harness and an incoming interface and both ports are specified, because
+there is no TCP/IP call to find the ident for. */
 
 if (host_checking)
   {
   int x[4];
   int size;
 
-  sender_ident = NULL;
-  if (running_in_test_harness && sender_host_port != 0 &&
-      interface_address != NULL && interface_port != 0)
-    verify_get_ident(1413);
+  if (!sender_ident_set)
+    {
+    sender_ident = NULL;
+    if (running_in_test_harness && sender_host_port != 0 &&
+        interface_address != NULL && interface_port != 0)
+      verify_get_ident(1413);
+    }
 
   /* In case the given address is a non-canonical IPv6 address, canonicize
   it. The code works for both IPv4 and IPv6, as it happens. */
@@ -4255,7 +4355,7 @@ sender_ident. */
 
 else if (is_inetd)
   {
-  fclose(stderr);
+  (void)fclose(stderr);
   exim_nullstd();                       /* Re-open to /dev/null */
   verify_get_ident(IDENT_PORT);
   host_build_sender_fullhost();
@@ -4285,7 +4385,7 @@ else if (!is_inetd) sender_host_unknown = TRUE;
 if exim is started from inetd. In this case fd 0 will be set to the socket,
 but fd 1 will not be set. This also happens for passed SMTP channels. */
 
-if (fstat(1, &statbuf) < 0) dup2(0, 1);
+if (fstat(1, &statbuf) < 0) (void)dup2(0, 1);
 
 /* Set up the incoming protocol name and the state of the program. Root
 is allowed to force received protocol via the -oMr option above, and if we are
@@ -4340,12 +4440,12 @@ if (smtp_input)
     }
   }
 
-/* Otherwise, set up the input size limit here */
+/* Otherwise, set up the input size limit here. */
 
 else
   {
-  thismessage_size_limit = expand_string_integer(message_size_limit);
-  if (thismessage_size_limit < 0)
+  thismessage_size_limit = expand_string_integer(message_size_limit, TRUE);
+  if (expand_string_message != NULL)
     {
     if (thismessage_size_limit == -1)
       log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to expand "
@@ -4556,6 +4656,19 @@ while (more)
         }
       }
 
+    /* Run the acl_not_smtp_start ACL if required. The result of the ACL is
+    ignored; rejecting here would just add complication, and it can just as
+    well be done later. Allow $recipients to be visible in the ACL. */
+
+    if (acl_not_smtp_start != NULL)
+      {
+      uschar *user_msg, *log_msg;
+      enable_dollar_recipients = TRUE;
+      (void)acl_check(ACL_WHERE_NOTSMTP_START, NULL, acl_not_smtp_start,
+        &user_msg, &log_msg);
+      enable_dollar_recipients = FALSE;
+      }
+
     /* Read the data for the message. If filter_test is not FTEST_NONE, this
     will just read the headers for the message, and not write anything onto the
     spool. */
@@ -4611,7 +4724,7 @@ while (more)
     if (ftest_prefix != NULL) printf("Prefix    = %s\n", ftest_prefix);
     if (ftest_suffix != NULL) printf("Suffix    = %s\n", ftest_suffix);
 
-    chdir("/");   /* Get away from wherever the user is running this from */
+    (void)chdir("/");   /* Get away from wherever the user is running this from */
 
     /* Now we run either a system filter test, or a user filter test, or both.
     In the latter case, headers added by the system filter will persist and be
@@ -4704,12 +4817,6 @@ while (more)
       close_unwanted();      /* Close unwanted file descriptors and TLS */
       exim_nullstd();        /* Ensure std{in,out,err} exist */
 
-      /* Occasionally in the test harness we don't have synchronous delivery
-      set (can happen with bounces). In that case, let the old process finish
-      before continuing, to keep the debug output the same. */
-
-      if (running_in_test_harness && !synchronous_delivery) millisleep(100);
-
       /* Re-exec Exim if we need to regain privilege (note: in mua_wrapper
       mode, deliver_drop_privilege is forced TRUE). */