Intercept chown()/fchown() failure and emit a pointer to the bugreport. Closes 2391
[exim.git] / src / src / deliver.c
index 9f292fc535ca7124f363ad9ad2fa6d88b6b3be91..696effdeedfea0a6c21d6ca756b25fe76f5858f4 100644 (file)
@@ -284,13 +284,12 @@ to the same pipe or file. */
 
 else
   {
-  address_item *addr2;
   if (testflag(addr, af_pfr))
     {
     if (testflag(addr, af_file))        address_file = addr->local_part;
     else if (addr->local_part[0] == '|') address_pipe = addr->local_part;
     }
-  for (addr2 = addr->next; addr2; addr2 = addr2->next)
+  for (address_item * addr2 = addr->next; addr2; addr2 = addr2->next)
     {
     if (deliver_domain && Ustrcmp(deliver_domain, addr2->domain) != 0)
       deliver_domain = NULL;
@@ -329,11 +328,9 @@ Returns:    a file descriptor, or -1 (with errno set)
 static int
 open_msglog_file(uschar *filename, int mode, uschar **error)
 {
-int fd, i;
-
-for (i = 2; i > 0; i--)
+for (int i = 2; i > 0; i--)
   {
-  fd = Uopen(filename,
+  int fd = Uopen(filename,
 #ifdef O_CLOEXEC
     O_CLOEXEC |
 #endif
@@ -350,7 +347,7 @@ for (i = 2; i > 0; i--)
 #ifndef O_CLOEXEC
     (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
 #endif
-    if (fchown(fd, exim_uid, exim_gid) < 0)
+    if (exim_fchown(fd, exim_uid, exim_gid, filename) < 0)
       {
       *error = US"chown";
       return -1;
@@ -370,7 +367,7 @@ for (i = 2; i > 0; i--)
                        MSGLOG_DIRECTORY_MODE, TRUE);
   }
 
-*error = US"create";
+*error = US"create or open";
 return -1;
 }
 
@@ -422,8 +419,7 @@ Returns:     nothing
 static void
 replicate_status(address_item *addr)
 {
-address_item *addr2;
-for (addr2 = addr->next; addr2; addr2 = addr2->next)
+for (address_item * addr2 = addr->next; addr2; addr2 = addr2->next)
   {
   addr2->transport =       addr->transport;
   addr2->transport_return = addr->transport_return;
@@ -657,8 +653,6 @@ Returns:      nothing
 static void
 address_done(address_item *addr, uschar *now)
 {
-address_item *dup;
-
 update_spool = TRUE;        /* Ensure spool gets updated */
 
 /* Top-level address */
@@ -685,7 +679,7 @@ else tree_add_nonrecipient(addr->unique);
 /* Check the list of duplicate addresses and ensure they are now marked
 done as well. */
 
-for (dup = addr_duplicate; dup; dup = dup->next)
+for (address_item * dup = addr_duplicate; dup; dup = dup->next)
   if (Ustrcmp(addr->unique, dup->unique) == 0)
     {
     tree_add_nonrecipient(dup->unique);
@@ -716,9 +710,10 @@ Returns:    nothing
 static void
 child_done(address_item *addr, uschar *now)
 {
-address_item *aa;
 while (addr->parent)
   {
+  address_item *aa;
+
   addr = addr->parent;
   if (--addr->child_count > 0) return;   /* Incomplete parent */
   address_done(addr, now);
@@ -761,10 +756,9 @@ d_log_interface(gstring * g)
 if (LOGGING(incoming_interface) && LOGGING(outgoing_interface)
     && sending_ip_address)
   {
-  g = string_append(g, 2, US" I=[", sending_ip_address);
-  g = LOGGING(outgoing_port)
-    ? string_append(g, 2, US"]:", string_sprintf("%d", sending_port))
-    : string_catn(g, US"]", 1);
+  g = string_fmt_append(g, " I=[%s]", sending_ip_address);
+  if (LOGGING(outgoing_port))
+    g = string_fmt_append(g, ":%d", sending_port);
   }
 return g;
 }
@@ -784,14 +778,14 @@ if (LOGGING(dnssec) && h->dnssec == DS_YES)
 g = string_append(g, 3, US" [", h->address, US"]");
 
 if (LOGGING(outgoing_port))
-  g = string_append(g, 2, US":", string_sprintf("%d", h->port));
+  g = string_fmt_append(g, ":%d", h->port);
 
 #ifdef SUPPORT_SOCKS
 if (LOGGING(proxy) && proxy_local_address)
   {
   g = string_append(g, 3, US" PRX=[", proxy_local_address, US"]");
   if (LOGGING(outgoing_port))
-    g = string_append(g, 2, US":", string_sprintf("%d", proxy_local_port));
+    g = string_fmt_append(g, ":%d", proxy_local_port);
   }
 #endif
 
@@ -862,6 +856,7 @@ if (action)
     {
     DEBUG(D_deliver)
       debug_printf("Event(%s): event_action returned \"%s\"\n", event, s);
+    errno = ERRNO_EVENT;
     return s;
     }
   }
@@ -1062,8 +1057,7 @@ if (  (all_parents || testflag(addr, af_pfr))
    && addr->parent != topaddr)
   {
   uschar *s = US" (";
-  address_item *addr2;
-  for (addr2 = addr->parent; addr2 != topaddr; addr2 = addr2->parent)
+  for (address_item * addr2 = addr->parent; addr2 != topaddr; addr2 = addr2->parent)
     {
     g = string_catn(g, s, 2);
     g = string_cat (g, addr2->address);
@@ -1196,8 +1190,7 @@ if (addr->router)
 g = string_append(g, 2, US" T=", addr->transport->name);
 
 if (LOGGING(delivery_size))
-  g = string_append(g, 2, US" S=",
-    string_sprintf("%d", transport_count));
+  g = string_fmt_append(g, " S=%d", transport_count);
 
 /* Local delivery */
 
@@ -1273,12 +1266,11 @@ if (  LOGGING(smtp_confirmation)
    && (addr->host_used || Ustrcmp(addr->transport->driver_name, "lmtp") == 0)
    )
   {
-  unsigned i;
   unsigned lim = big_buffer_size < 1024 ? big_buffer_size : 1024;
   uschar *p = big_buffer;
   uschar *ss = addr->message;
   *p++ = '\"';
-  for (i = 0; i < lim && ss[i] != 0; i++)      /* limit logged amount */
+  for (int i = 0; i < lim && ss[i] != 0; i++)  /* limit logged amount */
     {
     if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\'; /* quote \ and " */
     *p++ = ss[i];
@@ -1345,13 +1337,12 @@ if (driver_name)
   {
   if (driver_kind[1] == 't' && addr->router)
     g = string_append(g, 2, US" R=", addr->router->name);
-  g = string_cat(g, string_sprintf(" %c=%s", toupper(driver_kind[1]), driver_name));
+  g = string_fmt_append(g, " %c=%s", toupper(driver_kind[1]), driver_name);
   }
 else if (driver_kind)
   g = string_append(g, 2, US" ", driver_kind);
 
-/*XXX need an s+s+p sprintf */
-g = string_cat(g, string_sprintf(" defer (%d)", addr->basic_errno));
+g = string_fmt_append(g, " defer (%d)", addr->basic_errno);
 
 if (addr->basic_errno > 0)
   g = string_append(g, 2, US": ",
@@ -1365,8 +1356,7 @@ if (addr->host_used)
   if (LOGGING(outgoing_port))
     {
     int port = addr->host_used->port;
-    g = string_append(g, 2,
-         US":", port == PORT_NONE ? US"25" : string_sprintf("%d", port));
+    g = string_fmt_append(g, ":%d", port == PORT_NONE ? 25 : port);
     }
   }
 
@@ -1786,22 +1776,20 @@ Returns:       nothing
 static void
 common_error(BOOL logit, address_item *addr, int code, uschar *format, ...)
 {
-address_item *addr2;
 addr->basic_errno = code;
 
 if (format)
   {
   va_list ap;
-  uschar buffer[512];
+  gstring * g;
+
   va_start(ap, format);
-  if (!string_vformat(buffer, sizeof(buffer), CS format, ap))
-    log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-      "common_error expansion was longer than " SIZE_T_FMT, sizeof(buffer));
+  g = string_vformat(NULL, TRUE, CS format, ap);
   va_end(ap);
-  addr->message = string_copy(buffer);
+  addr->message = string_from_gstring(g);
   }
 
-for (addr2 = addr->next; addr2; addr2 = addr2->next)
+for (address_item * addr2 = addr->next; addr2; addr2 = addr2->next)
   {
   addr2->basic_errno = code;
   addr2->message = addr->message;
@@ -1831,9 +1819,8 @@ Returns:      TRUE if the uid is on the list
 static BOOL
 check_never_users(uid_t uid, uid_t *nusers)
 {
-int i;
 if (!nusers) return FALSE;
-for (i = 1; i <= (int)(nusers[0]); i++) if (nusers[i] == uid) return TRUE;
+for (int i = 1; i <= (int)(nusers[0]); i++) if (nusers[i] == uid) return TRUE;
 return FALSE;
 }
 
@@ -2378,9 +2365,8 @@ if ((pid = fork()) == 0)
 
   DEBUG(D_deliver)
     {
-    address_item *batched;
     debug_printf("  home=%s current=%s\n", deliver_home, working_directory);
-    for (batched = addr->next; batched; batched = batched->next)
+    for (address_item * batched = addr->next; batched; batched = batched->next)
       debug_printf("additional batched address: %s\n", batched->address);
     }
 
@@ -3118,12 +3104,7 @@ while (addr_local)
 
         DEBUG(D_deliver|D_transport)
           debug_printf("%s shadow transport returned %s for %s\n",
-            stp->name,
-            sresult == OK ?    "OK" :
-            sresult == DEFER ? "DEFER" :
-            sresult == FAIL ?  "FAIL" :
-            sresult == PANIC ? "PANIC" : "?",
-            shadow_addr->address);
+            stp->name, rc_to_string(sresult), shadow_addr->address);
         }
 
       DEBUG(D_deliver|D_transport)
@@ -3152,12 +3133,7 @@ while (addr_local)
 
     DEBUG(D_deliver|D_transport)
       debug_printf("%s transport returned %s for %s\n",
-        tp->name,
-        result == OK ?    "OK" :
-        result == DEFER ? "DEFER" :
-        result == FAIL ?  "FAIL" :
-        result == PANIC ? "PANIC" : "?",
-        addr2->address);
+        tp->name, rc_to_string(result), addr2->address);
 
     /* If there is a retry_record, or if delivery is deferred, build a retry
     item for setting a new retry time or deleting the old retry record from
@@ -3290,9 +3266,8 @@ while (  *aptr
 
 DEBUG(D_deliver)
   {
-  address_item *addr;
   debug_printf("remote addresses after sorting:\n");
-  for (addr = addr_remote; addr; addr = addr->next)
+  for (address_item * addr = addr_remote; addr; addr = addr->next)
     debug_printf("  %s\n", addr->address);
   }
 }
@@ -3797,12 +3772,10 @@ static void
 remote_post_process(address_item *addr, int logflags, uschar *msg,
   BOOL fallback)
 {
-host_item *h;
-
 /* If any host addresses were found to be unusable, add them to the unusable
 tree so that subsequent deliveries don't try them. */
 
-for (h = addr->host_list; h; h = h->next)
+for (host_item * h = addr->host_list; h; h = h->next)
   if (h->address)
     if (h->status >= hstatus_unusable) tree_add_unusable(h);
 
@@ -4236,7 +4209,6 @@ static BOOL
 do_remote_deliveries(BOOL fallback)
 {
 int parmax;
-int delivery_count;
 int poffset;
 
 parcount = 0;    /* Number of executing subprocesses */
@@ -4260,7 +4232,7 @@ if (!parlist)
 
 /* Now loop for each remote delivery */
 
-for (delivery_count = 0; addr_remote; delivery_count++)
+for (int delivery_count = 0; addr_remote; delivery_count++)
   {
   pid_t pid;
   uid_t uid;
@@ -4559,9 +4531,8 @@ for (delivery_count = 0; addr_remote; delivery_count++)
         && addr->host_list
         )
        {
-       host_item * h;
        ok = FALSE;
-       for (h = addr->host_list; h; h = h->next)
+       for (host_item * h = addr->host_list; h; h = h->next)
          if (Ustrcmp(h->name, continue_hostname) == 0)
   /*XXX should also check port here */
            { ok = TRUE; break; }
@@ -4613,12 +4584,9 @@ for (delivery_count = 0; addr_remote; delivery_count++)
     interface to the transport. */
 
     for (next = addr_remote; next && !f.continue_more; next = next->next)
-      {
-      host_item *h;
-      for (h = next->host_list; h; h = h->next)
+      for (host_item * h = next->host_list; h; h = h->next)
         if (Ustrcmp(h->name, continue_hostname) == 0)
           { f.continue_more = TRUE; break; }
-      }
     }
 
   /* The transports set up the process info themselves as they may connect
@@ -5267,15 +5235,12 @@ static int
 continue_closedown(void)
 {
 if (continue_transport)
-  {
-  transport_instance *t;
-  for (t = transports; t; t = t->next)
+  for (transport_instance * t = transports; t; t = t->next)
     if (Ustrcmp(t->name, continue_transport) == 0)
       {
       if (t->info->closedown) (t->info->closedown)(t);
       break;
       }
-  }
 return DELIVER_NOT_ATTEMPTED;
 }
 
@@ -5383,7 +5348,7 @@ static void
 print_address_error(address_item *addr, FILE *f, uschar *t)
 {
 int count = Ustrlen(t);
-uschar *s = testflag(addr, af_pass_message)? addr->message : NULL;
+uschar *s = testflag(addr, af_pass_message) ? addr->message : NULL;
 
 if (!s && !(s = addr->user_message))
   return;
@@ -5890,9 +5855,7 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT)
     ugid.uid_set = ugid.gid_set = TRUE;
     }
   else
-    {
     ugid.uid_set = ugid.gid_set = FALSE;
-    }
 
   return_path = sender_address;
   f.enable_dollar_recipients = TRUE;   /* Permit $recipients in system filter */
@@ -6299,9 +6262,8 @@ if (process_recipients != RECIP_IGNORE)
 
 DEBUG(D_deliver)
   {
-  address_item *p;
   debug_printf("Delivery address list:\n");
-  for (p = addr_new; p; p = p->next)
+  for (address_item * p = addr_new; p; p = p->next)
     debug_printf("  %s %s\n", p->address,
       p->onetime_parent ? p->onetime_parent : US"");
   }
@@ -6615,13 +6577,21 @@ while (addr_new)           /* Loop until all addresses dealt with */
       if (  domain_retry_record
          && now - domain_retry_record->time_stamp > retry_data_expire
         )
+       {
+       DEBUG(D_deliver|D_retry)
+         debug_printf("domain retry record present but expired\n");
         domain_retry_record = NULL;    /* Ignore if too old */
+       }
 
       address_retry_record = dbfn_read(dbm_file, addr->address_retry_key);
       if (  address_retry_record
          && now - address_retry_record->time_stamp > retry_data_expire
         )
+       {
+       DEBUG(D_deliver|D_retry)
+         debug_printf("address retry record present but expired\n");
         address_retry_record = NULL;   /* Ignore if too old */
+       }
 
       if (!address_retry_record)
         {
@@ -6630,7 +6600,11 @@ while (addr_new)           /* Loop until all addresses dealt with */
         address_retry_record = dbfn_read(dbm_file, altkey);
         if (  address_retry_record
           && now - address_retry_record->time_stamp > retry_data_expire)
+         {
+         DEBUG(D_deliver|D_retry)
+           debug_printf("address<sender> retry record present but expired\n");
           address_retry_record = NULL;   /* Ignore if too old */
+         }
         }
       }
     else
@@ -6639,9 +6613,18 @@ while (addr_new)           /* Loop until all addresses dealt with */
     DEBUG(D_deliver|D_retry)
       {
       if (!domain_retry_record)
-        debug_printf("no domain retry record\n");
+       debug_printf("no   domain  retry record\n");
+      else
+       debug_printf("have domain  retry record; next_try = now%+d\n",
+                     f.running_in_test_harness ? 0 :
+                     (int)(domain_retry_record->next_try - now));
+
       if (!address_retry_record)
-        debug_printf("no address retry record\n");
+       debug_printf("no   address retry record\n");
+      else
+       debug_printf("have address retry record; next_try = now%+d\n",
+                     f.running_in_test_harness ? 0 :
+                     (int)(address_retry_record->next_try - now));
       }
 
     /* If we are sending a message down an existing SMTP connection, we must
@@ -6663,6 +6646,9 @@ while (addr_new)           /* Loop until all addresses dealt with */
       addr->message = US"reusing SMTP connection skips previous routing defer";
       addr->basic_errno = ERRNO_RRETRY;
       (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+
+      addr->message = domain_retry_record->text;
+      setflag(addr, af_pass_message);
       }
 
     /* If we are in a queue run, defer routing unless there is no retry data or
@@ -6716,6 +6702,16 @@ while (addr_new)           /* Loop until all addresses dealt with */
       addr->message = US"retry time not reached";
       addr->basic_errno = ERRNO_RRETRY;
       (void)post_process_one(addr, DEFER, LOG_MAIN, EXIM_DTYPE_ROUTER, 0);
+
+      /* For remote-retry errors (here and just above) that we've not yet
+      hit the rery time, use the error recorded in the retry database
+      as info in the warning message.  This lets us send a message even
+      when we're not failing on a fresh attempt.  We assume that this
+      info is not sensitive. */
+
+      addr->message = domain_retry_record
+       ? domain_retry_record->text : address_retry_record->text;
+      setflag(addr, af_pass_message);
       }
 
     /* The domain is OK for routing. Remember if retry data exists so it
@@ -6911,22 +6907,21 @@ while (addr_new)           /* Loop until all addresses dealt with */
 
 DEBUG(D_deliver|D_retry|D_route)
   {
-  address_item *p;
   debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
   debug_printf("After routing:\n  Local deliveries:\n");
-  for (p = addr_local; p; p = p->next)
+  for (address_item * p = addr_local; p; p = p->next)
     debug_printf("    %s\n", p->address);
 
   debug_printf("  Remote deliveries:\n");
-  for (p = addr_remote; p; p = p->next)
+  for (address_item * p = addr_remote; p; p = p->next)
     debug_printf("    %s\n", p->address);
 
   debug_printf("  Failed addresses:\n");
-  for (p = addr_failed; p; p = p->next)
+  for (address_item * p = addr_failed; p; p = p->next)
     debug_printf("    %s\n", p->address);
 
   debug_printf("  Deferred addresses:\n");
-  for (p = addr_defer; p; p = p->next)
+  for (address_item * p = addr_defer; p; p = p->next)
     debug_printf("    %s\n", p->address);
   }
 
@@ -7046,7 +7041,7 @@ if (  f.header_rewritten
   }
 
 
-/* If there are any deliveries to be and we do not already have the journal
+/* If there are any deliveries to do and we do not already have the journal
 file, create it. This is used to record successful deliveries as soon as
 possible after each delivery is known to be complete. A file opened with
 O_APPEND is used so that several processes can run simultaneously.
@@ -7077,7 +7072,7 @@ if (addr_local || addr_remote)
     that the mode is correct - the group setting doesn't always seem to get
     set automatically. */
 
-    if(  fchown(journal_fd, exim_uid, exim_gid)
+    if(  exim_fchown(journal_fd, exim_uid, exim_gid, fname)
       || fchmod(journal_fd, SPOOL_MODE)
 #ifndef O_CLOEXEC
       || fcntl(journal_fd, F_SETFD, fcntl(journal_fd, F_GETFD) | FD_CLOEXEC)
@@ -7208,8 +7203,8 @@ if (mua_wrapper)
   {
   if (addr_defer)
     {
-    address_item *addr, *nextaddr;
-    for (addr = addr_defer; addr; addr = nextaddr)
+    address_item * nextaddr;
+    for (address_item * addr = addr_defer; addr; addr = nextaddr)
       {
       log_write(0, LOG_MAIN, "** %s mua_wrapper forced failure for deferred "
         "delivery", addr->address);
@@ -7406,7 +7401,7 @@ if (addr_senddsn)
 
     tctx.u.fd = fd;
     tctx.options = topt_add_return_path | topt_no_body;
-    /*XXX hmm, retval ignored.
+    /*XXX hmm, FALSE(fail) retval ignored.
     Could error for any number of reasons, and they are not handled. */
     transport_write_message(&tctx, 0);
     fflush(f);
@@ -8017,6 +8012,8 @@ the parent's domain.
 If all the deferred addresses have an error number that indicates "retry time
 not reached", skip sending the warning message, because it won't contain the
 reason for the delay. It will get sent at the next real delivery attempt.
+  Exception: for retries caused by a remote peer we use the error message
+  store in the retry DB as the reason.
 However, if at least one address has tried, we'd better include all of them in
 the message.
 
@@ -8034,18 +8031,17 @@ was set just to keep the message on the spool, so there is nothing to do here.
 
 else if (addr_defer != (address_item *)(+1))
   {
-  address_item *addr;
   uschar *recipients = US"";
-  BOOL delivery_attempted = FALSE;
+  BOOL want_warning_msg = FALSE;
 
   deliver_domain = testflag(addr_defer, af_pfr)
     ? addr_defer->parent->domain : addr_defer->domain;
 
-  for (addr = addr_defer; addr; addr = addr->next)
+  for (address_item * addr = addr_defer; addr; addr = addr->next)
     {
     address_item *otaddr;
 
-    if (addr->basic_errno > ERRNO_RETRY_BASE) delivery_attempted = TRUE;
+    if (addr->basic_errno > ERRNO_WARN_BASE) want_warning_msg = TRUE;
 
     if (deliver_domain)
       {
@@ -8117,7 +8113,7 @@ else if (addr_defer != (address_item *)(+1))
   it also defers). */
 
   if (  !f.queue_2stage
-     && delivery_attempted
+     && want_warning_msg
      && (  !(addr_defer->dsn_flags & rf_dsnflags)
         || addr_defer->dsn_flags & rf_notify_delay
        )
@@ -8168,7 +8164,7 @@ else if (addr_defer != (address_item *)(+1))
 
     DEBUG(D_deliver)
       {
-      debug_printf("time on queue = %s\n", readconf_printtime(queue_time));
+      debug_printf("time on queue = %s  id %s  addr %s\n", readconf_printtime(queue_time), message_id, addr_defer->address);
       debug_printf("warning counts: required %d done %d\n", count,
         warning_count);
       }
@@ -8511,11 +8507,6 @@ if (!regex_AUTH) regex_AUTH =
 #ifdef SUPPORT_TLS
 if (!regex_STARTTLS) regex_STARTTLS =
   regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE);
-
-# ifdef EXPERIMENTAL_REQUIRETLS
-if (!regex_REQUIRETLS) regex_REQUIRETLS =
-  regex_must_compile(US"\\n250[\\s\\-]REQUIRETLS(\\s|\\n|$)", FALSE, TRUE);
-# endif
 #endif
 
 if (!regex_CHUNKING) regex_CHUNKING =