+/* This function generates a list consisting of an address and its parents, for
+use in logging lines. For saved onetime aliased addresses, the onetime parent
+field is used. If the address was delivered by a transport with rcpt_include_
+affixes set, the af_include_affixes bit will be set in the address. In that
+case, we include the affixes here too.
+
+Arguments:
+ str points to start of growing string, or NULL
+ size points to current allocation for string
+ ptr points to offset for append point; updated on exit
+ addr bottom (ultimate) address
+ all_parents if TRUE, include all parents
+ success TRUE for successful delivery
+
+Returns: a growable string in dynamic store
+*/
+
+static uschar *
+string_log_address(uschar * str, int * size, int * ptr,
+ address_item *addr, BOOL all_parents, BOOL success)
+{
+BOOL add_topaddr = TRUE;
+address_item *topaddr;
+
+/* Find the ultimate parent */
+
+for (topaddr = addr; topaddr->parent; topaddr = topaddr->parent) ;
+
+/* We start with just the local part for pipe, file, and reply deliveries, and
+for successful local deliveries from routers that have the log_as_local flag
+set. File deliveries from filters can be specified as non-absolute paths in
+cases where the transport is going to complete the path. If there is an error
+before this happens (expansion failure) the local part will not be updated, and
+so won't necessarily look like a path. Add extra text for this case. */
+
+if ( testflag(addr, af_pfr)
+ || ( success
+ && addr->router && addr->router->log_as_local
+ && addr->transport && addr->transport->info->local
+ ) )
+ {
+ if (testflag(addr, af_file) && addr->local_part[0] != '/')
+ str = string_catn(str, size, ptr, CUS"save ", 5);
+ str = string_get_localpart(addr, str, size, ptr);
+ }
+
+/* Other deliveries start with the full address. It we have split it into local
+part and domain, use those fields. Some early failures can happen before the
+splitting is done; in those cases use the original field. */
+
+else
+ {
+ uschar * cmp = str + *ptr;
+
+ if (addr->local_part)
+ {
+ const uschar * s;
+ str = string_get_localpart(addr, str, size, ptr);
+ str = string_catn(str, size, ptr, US"@", 1);
+ s = addr->domain;
+#ifdef SUPPORT_I18N
+ if (testflag(addr, af_utf8_downcvt))
+ s = string_localpart_utf8_to_alabel(s, NULL);
+#endif
+ str = string_cat(str, size, ptr, s);
+ }
+ else
+ str = string_cat(str, size, ptr, addr->address);
+
+ /* If the address we are going to print is the same as the top address,
+ and all parents are not being included, don't add on the top address. First
+ of all, do a caseless comparison; if this succeeds, do a caseful comparison
+ on the local parts. */
+
+ str[*ptr] = 0;
+ if ( strcmpic(cmp, topaddr->address) == 0
+ && Ustrncmp(cmp, topaddr->address, Ustrchr(cmp, '@') - cmp) == 0
+ && !addr->onetime_parent
+ && (!all_parents || !addr->parent || addr->parent == topaddr)
+ )
+ add_topaddr = FALSE;
+ }
+
+/* If all parents are requested, or this is a local pipe/file/reply, and
+there is at least one intermediate parent, show it in brackets, and continue
+with all of them if all are wanted. */
+
+if ( (all_parents || testflag(addr, af_pfr))
+ && addr->parent
+ && addr->parent != topaddr)
+ {
+ uschar *s = US" (";
+ address_item *addr2;
+ for (addr2 = addr->parent; addr2 != topaddr; addr2 = addr2->parent)
+ {
+ str = string_catn(str, size, ptr, s, 2);
+ str = string_cat (str, size, ptr, addr2->address);
+ if (!all_parents) break;
+ s = US", ";
+ }
+ str = string_catn(str, size, ptr, US")", 1);
+ }
+
+/* Add the top address if it is required */
+
+if (add_topaddr)
+ str = string_append(str, size, ptr, 3,
+ US" <",
+ addr->onetime_parent ? addr->onetime_parent : topaddr->address,
+ US">");
+
+return str;
+}
+
+
+
+void
+timesince(struct timeval * diff, struct timeval * then)
+{
+gettimeofday(diff, NULL);
+diff->tv_sec -= then->tv_sec;
+if ((diff->tv_usec -= then->tv_usec) < 0)
+ {
+ diff->tv_sec--;
+ diff->tv_usec += 1000*1000;
+ }
+}
+
+
+
+static uschar *
+string_timediff(struct timeval * diff)
+{
+static uschar buf[sizeof("0.000s")];
+
+if (diff->tv_sec >= 5 || !LOGGING(millisec))
+ return readconf_printtime((int)diff->tv_sec);
+
+sprintf(CS buf, "%d.%03ds", (int)diff->tv_sec, (int)diff->tv_usec/1000);
+return buf;
+}
+
+
+uschar *
+string_timesince(struct timeval * then)
+{
+struct timeval diff;
+
+timesince(&diff, then);
+return string_timediff(&diff);
+}
+
+/******************************************************************************/
+
+
+
+/* If msg is NULL this is a delivery log and logchar is used. Otherwise
+this is a nonstandard call; no two-character delivery flag is written
+but sender-host and sender are prefixed and "msg" is inserted in the log line.
+
+Arguments:
+ flags passed to log_write()
+*/
+void
+delivery_log(int flags, address_item * addr, int logchar, uschar * msg)
+{
+int size = 256; /* Used for a temporary, */
+int ptr = 0; /* expanding buffer, for */
+uschar * s; /* building log lines; */
+void * reset_point; /* released afterwards. */
+
+/* Log the delivery on the main log. We use an extensible string to build up
+the log line, and reset the store afterwards. Remote deliveries should always
+have a pointer to the host item that succeeded; local deliveries can have a
+pointer to a single host item in their host list, for use by the transport. */
+
+#ifndef DISABLE_EVENT
+ /* presume no successful remote delivery */
+ lookup_dnssec_authenticated = NULL;
+#endif
+
+s = reset_point = store_get(size);
+
+if (msg)
+ s = string_append(s, &size, &ptr, 2, host_and_ident(TRUE), US" ");
+else
+ {
+ s[ptr++] = logchar;
+ s = string_catn(s, &size, &ptr, US"> ", 2);
+ }
+s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), TRUE);
+
+if (LOGGING(sender_on_delivery) || msg)
+ s = string_append(s, &size, &ptr, 3, US" F=<",
+#ifdef SUPPORT_I18N
+ testflag(addr, af_utf8_downcvt)
+ ? string_address_utf8_to_alabel(sender_address, NULL)
+ :
+#endif
+ sender_address,
+ US">");
+
+if (*queue_name)
+ s = string_append(s, &size, &ptr, 2, US" Q=", queue_name);
+
+#ifdef EXPERIMENTAL_SRS
+if(addr->prop.srs_sender)
+ s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->prop.srs_sender, US">");
+#endif
+
+/* You might think that the return path must always be set for a successful
+delivery; indeed, I did for some time, until this statement crashed. The case
+when it is not set is for a delivery to /dev/null which is optimised by not
+being run at all. */
+
+if (used_return_path && LOGGING(return_path_on_delivery))
+ s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
+
+if (msg)
+ s = string_append(s, &size, &ptr, 2, US" ", msg);
+
+/* For a delivery from a system filter, there may not be a router */
+if (addr->router)
+ s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
+
+s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
+
+if (LOGGING(delivery_size))
+ s = string_append(s, &size, &ptr, 2, US" S=",
+ string_sprintf("%d", transport_count));
+
+/* Local delivery */
+
+if (addr->transport->info->local)
+ {
+ if (addr->host_list)
+ s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name);
+ s = d_log_interface(s, &size, &ptr);
+ if (addr->shadow_message)
+ s = string_cat(s, &size, &ptr, addr->shadow_message);
+ }
+
+/* Remote delivery */
+
+else
+ {
+ if (addr->host_used)
+ {
+ s = d_hostlog(s, &size, &ptr, addr);
+ if (continue_sequence > 1)
+ s = string_catn(s, &size, &ptr, US"*", 1);
+
+#ifndef DISABLE_EVENT
+ deliver_host_address = addr->host_used->address;
+ deliver_host_port = addr->host_used->port;
+ deliver_host = addr->host_used->name;
+
+ /* DNS lookup status */
+ lookup_dnssec_authenticated = addr->host_used->dnssec==DS_YES ? US"yes"
+ : addr->host_used->dnssec==DS_NO ? US"no"
+ : NULL;
+#endif
+ }
+
+#ifdef SUPPORT_TLS
+ s = d_tlslog(s, &size, &ptr, addr);
+#endif
+
+ if (addr->authenticator)
+ {
+ s = string_append(s, &size, &ptr, 2, US" A=", addr->authenticator);
+ if (addr->auth_id)
+ {
+ s = string_append(s, &size, &ptr, 2, US":", addr->auth_id);
+ if (LOGGING(smtp_mailauth) && addr->auth_sndr)
+ s = string_append(s, &size, &ptr, 2, US":", addr->auth_sndr);
+ }
+ }
+
+#ifndef DISABLE_PRDR
+ if (testflag(addr, af_prdr_used))
+ s = string_catn(s, &size, &ptr, US" PRDR", 5);
+#endif
+
+ if (testflag(addr, af_chunking_used))
+ s = string_catn(s, &size, &ptr, US" K", 2);
+ }
+
+/* confirmation message (SMTP (host_used) and LMTP (driver_name)) */
+
+if ( LOGGING(smtp_confirmation)
+ && addr->message
+ && (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 */
+ {
+ if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\'; /* quote \ and " */
+ *p++ = ss[i];
+ }
+ *p++ = '\"';
+ *p = 0;
+ s = string_append(s, &size, &ptr, 2, US" C=", big_buffer);
+ }
+
+/* Time on queue and actual time taken to deliver */
+
+if (LOGGING(queue_time))
+ s = string_append(s, &size, &ptr, 2, US" QT=",
+ string_timesince(&received_time));
+
+if (LOGGING(deliver_time))
+ {
+ struct timeval diff = {.tv_sec = addr->more_errno, .tv_usec = addr->delivery_usec};
+ s = string_append(s, &size, &ptr, 2, US" DT=", string_timediff(&diff));
+ }
+
+/* string_cat() always leaves room for the terminator. Release the
+store we used to build the line after writing it. */
+
+s[ptr] = 0;
+log_write(0, flags, "%s", s);
+
+#ifndef DISABLE_EVENT
+if (!msg) msg_event_raise(US"msg:delivery", addr);
+#endif
+
+store_reset(reset_point);
+return;
+}
+
+
+
+static void
+deferral_log(address_item * addr, uschar * now,
+ int logflags, uschar * driver_name, uschar * driver_kind)
+{
+int size = 256; /* Used for a temporary, */
+int ptr = 0; /* expanding buffer, for */
+uschar * s; /* building log lines; */
+void * reset_point; /* released afterwards. */
+
+uschar ss[32];
+
+/* Build up the line that is used for both the message log and the main
+log. */
+
+s = reset_point = store_get(size);
+
+/* Create the address string for logging. Must not do this earlier, because
+an OK result may be changed to FAIL when a pipe returns text. */
+
+s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), FALSE);
+
+if (*queue_name)
+ s = string_append(s, &size, &ptr, 2, US" Q=", queue_name);
+
+/* Either driver_name contains something and driver_kind contains
+" router" or " transport" (note the leading space), or driver_name is
+a null string and driver_kind contains "routing" without the leading
+space, if all routing has been deferred. When a domain has been held,
+so nothing has been done at all, both variables contain null strings. */
+
+if (driver_name)
+ {
+ if (driver_kind[1] == 't' && addr->router)
+ s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
+ Ustrcpy(ss, " ?=");
+ ss[1] = toupper(driver_kind[1]);
+ s = string_append(s, &size, &ptr, 2, ss, driver_name);
+ }
+else if (driver_kind)
+ s = string_append(s, &size, &ptr, 2, US" ", driver_kind);
+
+/*XXX need an s+s+p sprintf */
+sprintf(CS ss, " defer (%d)", addr->basic_errno);
+s = string_cat(s, &size, &ptr, ss);
+
+if (addr->basic_errno > 0)
+ s = string_append(s, &size, &ptr, 2, US": ",
+ US strerror(addr->basic_errno));
+
+if (addr->host_used)
+ {
+ s = string_append(s, &size, &ptr, 5,
+ US" H=", addr->host_used->name,
+ US" [", addr->host_used->address, US"]");
+ if (LOGGING(outgoing_port))
+ {
+ int port = addr->host_used->port;
+ s = string_append(s, &size, &ptr, 2,
+ US":", port == PORT_NONE ? US"25" : string_sprintf("%d", port));
+ }
+ }
+
+if (addr->message)
+ s = string_append(s, &size, &ptr, 2, US": ", addr->message);
+
+s[ptr] = 0;
+
+/* Log the deferment in the message log, but don't clutter it
+up with retry-time defers after the first delivery attempt. */
+
+if (deliver_firsttime || addr->basic_errno > ERRNO_RETRY_BASE)
+ deliver_msglog("%s %s\n", now, s);
+
+/* Write the main log and reset the store.
+For errors of the type "retry time not reached" (also remotes skipped
+on queue run), logging is controlled by L_retry_defer. Note that this kind
+of error number is negative, and all the retry ones are less than any
+others. */
+
+
+log_write(addr->basic_errno <= ERRNO_RETRY_BASE ? L_retry_defer : 0, logflags,
+ "== %s", s);
+
+store_reset(reset_point);
+return;
+}
+
+
+
+static void
+failure_log(address_item * addr, uschar * driver_kind, uschar * now)
+{
+int size = 256; /* Used for a temporary, */
+int ptr = 0; /* expanding buffer, for */
+uschar * s; /* building log lines; */
+void * reset_point; /* released afterwards. */
+
+/* Build up the log line for the message and main logs */
+
+s = reset_point = store_get(size);
+
+/* Create the address string for logging. Must not do this earlier, because
+an OK result may be changed to FAIL when a pipe returns text. */
+
+s = string_log_address(s, &size, &ptr, addr, LOGGING(all_parents), FALSE);
+
+if (LOGGING(sender_on_delivery))
+ s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">");
+
+if (*queue_name)
+ s = string_append(s, &size, &ptr, 2, US" Q=", queue_name);
+
+/* Return path may not be set if no delivery actually happened */
+
+if (used_return_path && LOGGING(return_path_on_delivery))
+ s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">");
+
+if (addr->router)
+ s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name);
+if (addr->transport)
+ s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name);
+
+if (addr->host_used)
+ s = d_hostlog(s, &size, &ptr, addr);
+
+#ifdef SUPPORT_TLS
+s = d_tlslog(s, &size, &ptr, addr);
+#endif