CVE-2020-28007: Link attack in Exim's log directory
[exim.git] / src / src / exim.c
index 067c47f8019bc6dc20de8ad3ae4abc2752291373..29e457fe276775360ec50acd24c8cb4da5362a85 100644 (file)
@@ -233,18 +233,8 @@ int fd;
 
 os_restarting_signal(sig, usr1_handler);
 
-if ((fd = Uopen(process_log_path, O_APPEND|O_WRONLY, LOG_MODE)) < 0)
-  {
-  /* If we are already running as the Exim user, try to create it in the
-  current process (assuming spool_directory exists). Otherwise, if we are
-  root, do the creation in an exim:exim subprocess. */
-
-  int euid = geteuid();
-  if (euid == exim_uid)
-    fd = Uopen(process_log_path, O_CREAT|O_APPEND|O_WRONLY, LOG_MODE);
-  else if (euid == root_uid)
-    fd = log_create_as_exim(process_log_path);
-  }
+if (!process_log_path) return;
+fd = log_open_as_exim(process_log_path);
 
 /* If we are neither exim nor root, or if we failed to create the log file,
 give up. There is not much useful we can do with errors, since we don't want
@@ -762,18 +752,18 @@ exit(EXIT_FAILURE);
 }
 
 /* fail if a length is too long */
-static void
+static inline void
 exim_len_fail_toolong(int itemlen, int maxlen, const char *description)
 {
 if (itemlen <= maxlen)
   return;
 fprintf(stderr, "exim: length limit exceeded (%d > %d) for: %s\n",
-        len, maxlen, description)
+        itemlen, maxlen, description);
 exit(EXIT_FAILURE);
 }
 
 /* only pass through the string item back to the caller if it's short enough */
-static const uschar *
+static inline const uschar *
 exim_str_fail_toolong(const uschar *item, int maxlen, const char *description)
 {
 exim_len_fail_toolong(Ustrlen(item), maxlen, description);
@@ -1554,10 +1544,11 @@ Arguments:
 */
 
 static void
-expansion_test_line(uschar * line)
+expansion_test_line(const uschar * line)
 {
 int len;
 BOOL dummy_macexp;
+uschar * s;
 
 Ustrncpy(big_buffer, line, big_buffer_size);
 big_buffer[big_buffer_size-1] = '\0';
@@ -1571,7 +1562,7 @@ if (isupper(big_buffer[0]))
     printf("Defined macro '%s'\n", mlast->name);
   }
 else
-  if ((line = expand_string(big_buffer))) printf("%s\n", CS line);
+  if ((s = expand_string(big_buffer))) printf("%s\n", CS s);
   else printf("Failed: %s\n", expand_string_message);
 }
 
@@ -1654,10 +1645,10 @@ uschar *cmdline_syslog_name = NULL;
 uschar *start_queue_run_id = NULL;
 uschar *stop_queue_run_id = NULL;
 uschar *expansion_test_message = NULL;
-uschar *ftest_domain = NULL;
-uschar *ftest_localpart = NULL;
-uschar *ftest_prefix = NULL;
-uschar *ftest_suffix = NULL;
+const uschar *ftest_domain = NULL;
+const uschar *ftest_localpart = NULL;
+const uschar *ftest_prefix = NULL;
+const uschar *ftest_suffix = NULL;
 uschar *log_oneline = NULL;
 uschar *malware_test_file = NULL;
 uschar *real_sender_address;
@@ -1751,7 +1742,7 @@ if (f.running_in_test_harness)
   debug_store = TRUE;
 
 /* Protect against abusive argv[0] */
-exim_len_fail_toolong(argv[0], PATH_MAX, "argv[0]");
+exim_str_fail_toolong(argv[0], PATH_MAX, "argv[0]");
 
 /* The C standard says that the equivalent of setlocale(LC_ALL, "C") is obeyed
 at the start of a program; however, it seems that some environments do not
@@ -3206,6 +3197,10 @@ on the second character (the one after '-'), to save some effort. */
         -oPX:       delete pid file of daemon */
 
       case 'P':
+       if (!f.running_in_test_harness && real_uid != root_uid && real_uid != exim_uid)
+         exim_fail("exim: only uid=%d or uid=%d can use -oP and -oPX "
+                    "(uid=%d euid=%d | %d)\n",
+                    root_uid, exim_uid, getuid(), geteuid(), real_uid);
        if (!*argrest) override_pid_file_path = argv[++i];
        else if (Ustrcmp(argrest, "X") == 0) delete_pid_file();
        else badarg = TRUE;
@@ -3235,7 +3230,7 @@ on the second character (the one after '-'), to save some effort. */
 
       case 'X':
        if (*argrest) badarg = TRUE;
-       else override_local_interfaces = string_copy_taint(exim_str_fail_toolong(argv[++i], 1024, "-oX", TRUE);
+       else override_local_interfaces = string_copy_taint(exim_str_fail_toolong(argv[++i], 1024, "-oX"), TRUE);
        break;
 
       /* Unknown -o argument */
@@ -3365,7 +3360,10 @@ on the second character (the one after '-'), to save some effort. */
 
 
     case 'R':   /* Synonymous with -qR... */
-    receiving_message = FALSE;
+      {
+      const uschar *tainted_selectstr;
+
+      receiving_message = FALSE;
 
     /* -Rf:   As -R (below) but force all deliveries,
        -Rff:  Ditto, but also thaw all frozen messages,
@@ -3376,29 +3374,29 @@ on the second character (the one after '-'), to save some effort. */
     in all cases provided there are no further characters in this
     argument. */
 
-    if (*argrest)
-      for (int i = 0; i < nelem(rsopts); i++)
-        if (Ustrcmp(argrest, rsopts[i]) == 0)
-          {
-          if (i != 2) f.queue_run_force = TRUE;
-          if (i >= 2) f.deliver_selectstring_regex = TRUE;
-          if (i == 1 || i == 4) f.deliver_force_thaw = TRUE;
-          argrest += Ustrlen(rsopts[i]);
-          }
+      if (*argrest)
+       for (int i = 0; i < nelem(rsopts); i++)
+         if (Ustrcmp(argrest, rsopts[i]) == 0)
+           {
+           if (i != 2) f.queue_run_force = TRUE;
+           if (i >= 2) f.deliver_selectstring_regex = TRUE;
+           if (i == 1 || i == 4) f.deliver_force_thaw = TRUE;
+           argrest += Ustrlen(rsopts[i]);
+           }
 
     /* -R: Set string to match in addresses for forced queue run to
     pick out particular messages. */
 
-    /* Avoid attacks from people providing very long strings, and do so before
-    we make copies. */
-    const char *tainted_selectstr;
-    if (*argrest)
-      tainted_selectstr = argrest;
-    else if (i+1 < argc)
-      tainted_selectstr = argv[++i];
-    else
-      exim_fail("exim: string expected after -R\n");
-    deliver_selectstring = string_copy_taint(exim_str_fail_toolong(tainted_selectstr, EXIM_EMAILADDR_MAX, "-R"), TRUE);
+      /* Avoid attacks from people providing very long strings, and do so before
+      we make copies. */
+      if (*argrest)
+       tainted_selectstr = argrest;
+      else if (i+1 < argc)
+       tainted_selectstr = argv[++i];
+      else
+       exim_fail("exim: string expected after -R\n");
+      deliver_selectstring = string_copy_taint(exim_str_fail_toolong(tainted_selectstr, EXIM_EMAILADDR_MAX, "-R"), TRUE);
+      }
     break;
 
     /* -r: an obsolete synonym for -f (see above) */
@@ -3407,7 +3405,10 @@ on the second character (the one after '-'), to save some effort. */
     /* -S: Like -R but works on sender. */
 
     case 'S':   /* Synonymous with -qS... */
-    receiving_message = FALSE;
+      {
+      const uschar *tainted_selectstr;
+
+      receiving_message = FALSE;
 
     /* -Sf:   As -S (below) but force all deliveries,
        -Sff:  Ditto, but also thaw all frozen messages,
@@ -3418,27 +3419,27 @@ on the second character (the one after '-'), to save some effort. */
     in all cases provided there are no further characters in this
     argument. */
 
-    if (*argrest)
-      for (int i = 0; i < nelem(rsopts); i++)
-        if (Ustrcmp(argrest, rsopts[i]) == 0)
-          {
-          if (i != 2) f.queue_run_force = TRUE;
-          if (i >= 2) f.deliver_selectstring_sender_regex = TRUE;
-          if (i == 1 || i == 4) f.deliver_force_thaw = TRUE;
-          argrest += Ustrlen(rsopts[i]);
-          }
+      if (*argrest)
+       for (int i = 0; i < nelem(rsopts); i++)
+         if (Ustrcmp(argrest, rsopts[i]) == 0)
+           {
+           if (i != 2) f.queue_run_force = TRUE;
+           if (i >= 2) f.deliver_selectstring_sender_regex = TRUE;
+           if (i == 1 || i == 4) f.deliver_force_thaw = TRUE;
+           argrest += Ustrlen(rsopts[i]);
+           }
 
     /* -S: Set string to match in addresses for forced queue run to
     pick out particular messages. */
 
-    const char *tainted_selectstr;
-    if (*argrest)
-      tainted_selectstr = argrest;
-    else if (i+1 < argc)
-      tainted_selectstr = argv[++i];
-    else
-      exim_fail("exim: string expected after -S\n");
-    deliver_selectstring_sender = string_copy_taint(exim_str_fail_toolong(tainted_selectstr, EXIM_EMAILADDR_MAX, "-S"), TRUE);
+      if (*argrest)
+       tainted_selectstr = argrest;
+      else if (i+1 < argc)
+       tainted_selectstr = argv[++i];
+      else
+       exim_fail("exim: string expected after -S\n");
+      deliver_selectstring_sender = string_copy_taint(exim_str_fail_toolong(tainted_selectstr, EXIM_EMAILADDR_MAX, "-S"), TRUE);
+      }
     break;
 
     /* -Tqt is an option that is exclusively for use by the testing suite.
@@ -3828,7 +3829,6 @@ during readconf_main() some expansion takes place already. */
 
 /* Store the initial cwd before we change directories.  Can be NULL if the
 dir has already been unlinked. */
-errno = 0;
 initial_cwd = os_getcwd(NULL, 0);
 if (!initial_cwd && errno)
   exim_fail("exim: getting initial cwd failed: %s\n", strerror(errno));
@@ -4122,11 +4122,9 @@ if (  (debug_selector & D_any  ||  LOGGING(arguments))
     p += 13;
   else
     {
-    Ustrncpy(p + 4, initial_cwd, big_buffer_size-5);
-    p += 4 + Ustrlen(initial_cwd);
-    /* in case p is near the end and we don't provide enough space for
-     * string_format to be willing to write. */
-    *p = '\0';
+    p += 4;
+    snprintf(CS p, big_buffer_size - (p - big_buffer), "%s", CCS initial_cwd);
+    p += Ustrlen(CCS p);
     }
 
   (void)string_format(p, big_buffer_size - (p - big_buffer), " %d args:", argc);
@@ -4500,7 +4498,7 @@ if (test_retry_arg >= 0)
   retry_config *yield;
   int basic_errno = 0;
   int more_errno = 0;
-  uschar *s1, *s2;
+  const uschar *s1, *s2;
 
   if (test_retry_arg >= argc)
     {
@@ -4529,7 +4527,7 @@ if (test_retry_arg >= 0)
 
   if (test_retry_arg < argc)
     {
-    uschar *ss = exim_str_fail_toolong(argv[test_retry_arg], EXIM_DRIVERNAME_MAX, "-brt 3rd");
+    const uschar *ss = exim_str_fail_toolong(argv[test_retry_arg], EXIM_DRIVERNAME_MAX, "-brt 3rd");
     uschar *error =
       readconf_retry_error(ss, ss + Ustrlen(ss), &basic_errno, &more_errno);
     if (error != NULL)
@@ -5015,11 +5013,15 @@ if (expansion_test)
   dns_init(FALSE, FALSE, FALSE);
   if (msg_action_arg > 0 && msg_action == MSG_LOAD)
     {
-    uschar spoolname[SPOOL_NAME_LENGTH];  /* Not big_buffer; used in spool_read_header() */
+    uschar * spoolname;
     if (!f.admin_user)
       exim_fail("exim: permission denied\n");
-    message_id = exim_str_fail_toolong(argv[msg_action_arg], MESSAGE_ID_LENGTH, "message-id");
-    (void)string_format(spoolname, sizeof(spoolname), "%s-H", message_id);
+    message_id = US exim_str_fail_toolong(argv[msg_action_arg], MESSAGE_ID_LENGTH, "message-id");
+    /* Checking the length of the ID is sufficient to validate it.
+    Get an untainted version so file opens can be done. */
+    message_id = string_copy_taint(message_id, FALSE);
+
+    spoolname = string_sprintf("%s-H", message_id);
     if ((deliver_datafile = spool_open_datafile(message_id)) < 0)
       printf ("Failed to load message datafile %s\n", message_id);
     if (spool_read_header(spoolname, TRUE, FALSE) != spool_read_OK)
@@ -5640,10 +5642,10 @@ while (more)
     {
     deliver_domain = ftest_domain ? ftest_domain : qualify_domain_recipient;
     deliver_domain_orig = deliver_domain;
-    deliver_localpart = ftest_localpart ? ftest_localpart : originator_login;
+    deliver_localpart = ftest_localpart ? US ftest_localpart : originator_login;
     deliver_localpart_orig = deliver_localpart;
-    deliver_localpart_prefix = ftest_prefix;
-    deliver_localpart_suffix = ftest_suffix;
+    deliver_localpart_prefix = US ftest_prefix;
+    deliver_localpart_suffix = US ftest_suffix;
     deliver_home = originator_home;
 
     if (!return_path)