X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/4c04137d73637107669e02b21f890387aaa2ef34..9e70917d0aa5e51f584b2af69ce80df458ac5c79:/src/src/deliver.c diff --git a/src/src/deliver.c b/src/src/deliver.c index 85208457f..b8a55b20a 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -9,6 +9,7 @@ #include "exim.h" +#include "transports/smtp.h" #include @@ -381,6 +382,7 @@ for (addr2 = addr->next; addr2; addr2 = addr2->next) addr2->transport_return = addr->transport_return; addr2->basic_errno = addr->basic_errno; addr2->more_errno = addr->more_errno; + addr2->delivery_usec = addr->delivery_usec; addr2->special_action = addr->special_action; addr2->message = addr->message; addr2->user_message = addr->user_message; @@ -671,7 +673,7 @@ address_item *aa; while (addr->parent) { addr = addr->parent; - if ((addr->child_count -= 1) > 0) return; /* Incomplete parent */ + if (--addr->child_count > 0) return; /* Incomplete parent */ address_done(addr, now); /* Log the completion of all descendents only when there is no ancestor with @@ -750,7 +752,12 @@ if (LOGGING(proxy) && proxy_local_address) } #endif -return d_log_interface(s, sp, pp); +s = d_log_interface(s, sp, pp); + +if (testflag(addr, af_tcp_fastopen)) + s = string_catn(s, sp, pp, US" TFO", 4); + +return s; } @@ -1028,6 +1035,43 @@ 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); +} + /******************************************************************************/ @@ -1156,11 +1200,11 @@ else } #ifndef DISABLE_PRDR - if (addr->flags & af_prdr_used) + if (testflag(addr, af_prdr_used)) s = string_catn(s, &size, &ptr, US" PRDR", 5); #endif - if (addr->flags & af_chunking_used) + if (testflag(addr, af_chunking_used)) s = string_catn(s, &size, &ptr, US" K", 2); } @@ -1190,11 +1234,13 @@ if ( LOGGING(smtp_confirmation) if (LOGGING(queue_time)) s = string_append(s, &size, &ptr, 2, US" QT=", - readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) ); + string_timesince(&received_time)); if (LOGGING(deliver_time)) - s = string_append(s, &size, &ptr, 2, US" DT=", - readconf_printtime(addr->more_errno)); + { + 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. */ @@ -1616,7 +1662,7 @@ else later (with a log entry). */ if (!*sender_address && message_age >= ignore_bounce_errors_after) - setflag(addr, af_ignore_error); + addr->prop.ignore_error = TRUE; /* Freeze the message if requested, or if this is a bounce message (or other message with null sender) and this address does not have its own errors @@ -1624,7 +1670,7 @@ else to ignore occurs later, instead of sending a message. Logging of freezing occurs later, just before writing the -H file. */ - if ( !testflag(addr, af_ignore_error) + if ( !addr->prop.ignore_error && ( addr->special_action == SPECIAL_FREEZE || (sender_address[0] == 0 && !addr->prop.errors_address) ) ) @@ -2346,6 +2392,7 @@ if ((pid = fork()) == 0) || (ret = write(pfd[pipe_write], &addr2->flags, sizeof(addr2->flags))) != sizeof(addr2->flags) || (ret = write(pfd[pipe_write], &addr2->basic_errno, sizeof(int))) != sizeof(int) || (ret = write(pfd[pipe_write], &addr2->more_errno, sizeof(int))) != sizeof(int) + || (ret = write(pfd[pipe_write], &addr2->delivery_usec, sizeof(int))) != sizeof(int) || (ret = write(pfd[pipe_write], &addr2->special_action, sizeof(int))) != sizeof(int) || (ret = write(pfd[pipe_write], &addr2->transport, sizeof(transport_instance *))) != sizeof(transport_instance *) @@ -2413,6 +2460,7 @@ for (addr2 = addr; addr2; addr2 = addr2->next) len = read(pfd[pipe_read], &addr2->flags, sizeof(addr2->flags)); len = read(pfd[pipe_read], &addr2->basic_errno, sizeof(int)); len = read(pfd[pipe_read], &addr2->more_errno, sizeof(int)); + len = read(pfd[pipe_read], &addr2->delivery_usec, sizeof(int)); len = read(pfd[pipe_read], &addr2->special_action, sizeof(int)); len = read(pfd[pipe_read], &addr2->transport, sizeof(transport_instance *)); @@ -2638,8 +2686,8 @@ time_t now = time(NULL); while (addr_local) { - time_t delivery_start; - int deliver_time; + struct timeval delivery_start; + struct timeval deliver_time; address_item *addr2, *addr3, *nextaddr; int logflags = LOG_MAIN; int logchar = dont_deliver? '*' : '='; @@ -2734,7 +2782,8 @@ while (addr_local) BOOL ok = tp == next->transport && !previously_transported(next, TRUE) - && (addr->flags & (af_pfr|af_file)) == (next->flags & (af_pfr|af_file)) + && testflag(addr, af_pfr) == testflag(next, af_pfr) + && testflag(addr, af_file) == testflag(next, af_file) && (!uses_lp || Ustrcmp(next->local_part, addr->local_part) == 0) && (!uses_dom || Ustrcmp(next->domain, addr->domain) == 0) && same_strings(next->prop.errors_address, addr->prop.errors_address) @@ -2935,9 +2984,10 @@ while (addr_local) single delivery. */ deliver_set_expansions(addr); - delivery_start = time(NULL); + + gettimeofday(&delivery_start, NULL); deliver_local(addr, FALSE); - deliver_time = (int)(time(NULL) - delivery_start); + timesince(&deliver_time, &delivery_start); /* If a shadow transport (which must perforce be another local transport), is defined, and its condition is met, we must pass the message to the shadow @@ -2975,13 +3025,13 @@ while (addr_local) addr3 = store_get(sizeof(address_item)); *addr3 = *addr2; addr3->next = NULL; - addr3->shadow_message = (uschar *) &(addr2->shadow_message); + addr3->shadow_message = US &addr2->shadow_message; addr3->transport = stp; addr3->transport_return = DEFER; addr3->return_filename = NULL; addr3->return_file = -1; *last = addr3; - last = &(addr3->next); + last = &addr3->next; } /* If we found any addresses to shadow, run the delivery, and stick any @@ -3074,7 +3124,11 @@ while (addr_local) /* Done with this address */ - if (result == OK) addr2->more_errno = deliver_time; + if (result == OK) + { + addr2->more_errno = deliver_time.tv_sec; + addr2->delivery_usec = deliver_time.tv_usec; + } post_process_one(addr2, result, logflags, DTYPE_TRANSPORT, logchar); /* If a pipe delivery generated text to be sent back, the result may be @@ -3238,7 +3292,7 @@ uschar *endptr = big_buffer; uschar *ptr = endptr; uschar *msg = p->msg; BOOL done = p->done; -BOOL unfinished = TRUE; +BOOL finished = FALSE; /* minimum size to read is header size including id, subid and length */ int required = PIPE_HEADER_SIZE; @@ -3271,7 +3325,7 @@ while (!done) There will be only one read if we get all the available data (i.e. don't fill the buffer completely). */ - if (remaining < required && unfinished) + if (remaining < required && !finished) { int len; int available = big_buffer_size - remaining; @@ -3301,11 +3355,11 @@ while (!done) /* If the length is zero (eof or no-more-data), just process what we already have. Note that if the process is still running and we have read all the data in the pipe (but less that "available") then we - won't read any more, as "unfinished" will get set FALSE. */ + won't read any more, as "finished" will get set. */ endptr += len; remaining += len; - unfinished = len == available; + finished = len != available; } /* If we are at the end of the available data, exit the loop. */ @@ -3326,8 +3380,8 @@ while (!done) } DEBUG(D_deliver) - debug_printf("header read id:%c,subid:%c,size:%s,required:%d,remaining:%d,unfinished:%d\n", - id, subid, header+2, required, remaining, unfinished); + debug_printf("header read id:%c,subid:%c,size:%s,required:%d,remaining:%d,finished:%d\n", + id, subid, header+2, required, remaining, finished); /* is there room for the dataset we want to read ? */ if (required > big_buffer_size - PIPE_HEADER_SIZE) @@ -3339,22 +3393,22 @@ while (!done) break; } - /* we wrote all datasets with atomic write() calls - remaining < required only happens if big_buffer was too small - to get all available data from pipe. unfinished has to be true - as well. */ + /* We wrote all datasets with atomic write() calls. Remaining < required only + happens if big_buffer was too small to get all available data from pipe; + finished has to be false as well. */ + if (remaining < required) { - if (unfinished) + if (!finished) continue; msg = string_sprintf("failed to read pipe from transport process " - "%d for transport %s: required size=%d > remaining size=%d and unfinished=false", + "%d for transport %s: required size=%d > remaining size=%d and finished=true", pid, addr->transport->driver_name, required, remaining); done = TRUE; break; } - /* step behind the header */ + /* Step past the header */ ptr += PIPE_HEADER_SIZE; /* Handle each possible type of item, assuming the complete item is @@ -3366,15 +3420,15 @@ while (!done) up by checking the IP address. */ case 'H': - for (h = addrlist->host_list; h; h = h->next) - { - if (!h->address || Ustrcmp(h->address, ptr+2) != 0) continue; - h->status = ptr[0]; - h->why = ptr[1]; - } - ptr += 2; - while (*ptr++); - break; + for (h = addrlist->host_list; h; h = h->next) + { + if (!h->address || Ustrcmp(h->address, ptr+2) != 0) continue; + h->status = ptr[0]; + h->why = ptr[1]; + } + ptr += 2; + while (*ptr++); + break; /* Retry items are sent in a preceding R item for each address. This is kept separate to keep each message short enough to guarantee it won't @@ -3388,62 +3442,61 @@ while (!done) that a "delete" item is dropped in favour of an "add" item. */ case 'R': - if (!addr) goto ADDR_MISMATCH; + if (!addr) goto ADDR_MISMATCH; - DEBUG(D_deliver|D_retry) - debug_printf("reading retry information for %s from subprocess\n", - ptr+1); + DEBUG(D_deliver|D_retry) + debug_printf("reading retry information for %s from subprocess\n", + ptr+1); - /* Cut out any "delete" items on the list. */ + /* Cut out any "delete" items on the list. */ - for (rp = &(addr->retries); (r = *rp); rp = &r->next) - if (Ustrcmp(r->key, ptr+1) == 0) /* Found item with same key */ - { - if ((r->flags & rf_delete) == 0) break; /* It was not "delete" */ - *rp = r->next; /* Excise a delete item */ - DEBUG(D_deliver|D_retry) - debug_printf(" existing delete item dropped\n"); - } + for (rp = &addr->retries; (r = *rp); rp = &r->next) + if (Ustrcmp(r->key, ptr+1) == 0) /* Found item with same key */ + { + if (!(r->flags & rf_delete)) break; /* It was not "delete" */ + *rp = r->next; /* Excise a delete item */ + DEBUG(D_deliver|D_retry) + debug_printf(" existing delete item dropped\n"); + } - /* We want to add a delete item only if there is no non-delete item; - however we still have to step ptr through the data. */ + /* We want to add a delete item only if there is no non-delete item; + however we still have to step ptr through the data. */ - if (!r || (*ptr & rf_delete) == 0) - { - r = store_get(sizeof(retry_item)); - r->next = addr->retries; - addr->retries = r; - r->flags = *ptr++; - r->key = string_copy(ptr); - while (*ptr++); - memcpy(&(r->basic_errno), ptr, sizeof(r->basic_errno)); - ptr += sizeof(r->basic_errno); - memcpy(&(r->more_errno), ptr, sizeof(r->more_errno)); - ptr += sizeof(r->more_errno); - r->message = (*ptr)? string_copy(ptr) : NULL; - DEBUG(D_deliver|D_retry) - debug_printf(" added %s item\n", - ((r->flags & rf_delete) == 0)? "retry" : "delete"); - } + if (!r || !(*ptr & rf_delete)) + { + r = store_get(sizeof(retry_item)); + r->next = addr->retries; + addr->retries = r; + r->flags = *ptr++; + r->key = string_copy(ptr); + while (*ptr++); + memcpy(&r->basic_errno, ptr, sizeof(r->basic_errno)); + ptr += sizeof(r->basic_errno); + memcpy(&r->more_errno, ptr, sizeof(r->more_errno)); + ptr += sizeof(r->more_errno); + r->message = *ptr ? string_copy(ptr) : NULL; + DEBUG(D_deliver|D_retry) debug_printf(" added %s item\n", + r->flags & rf_delete ? "delete" : "retry"); + } - else - { - DEBUG(D_deliver|D_retry) - debug_printf(" delete item not added: non-delete item exists\n"); - ptr++; - while(*ptr++); - ptr += sizeof(r->basic_errno) + sizeof(r->more_errno); - } + else + { + DEBUG(D_deliver|D_retry) + debug_printf(" delete item not added: non-delete item exists\n"); + ptr++; + while(*ptr++); + ptr += sizeof(r->basic_errno) + sizeof(r->more_errno); + } - while(*ptr++); - break; + while(*ptr++); + break; /* Put the amount of data written into the parlist block */ case 'S': - memcpy(&(p->transport_count), ptr, sizeof(transport_count)); - ptr += sizeof(transport_count); - break; + memcpy(&(p->transport_count), ptr, sizeof(transport_count)); + ptr += sizeof(transport_count); + break; /* Address items are in the order of items on the address chain. We remember the current address value in case this function is called @@ -3454,164 +3507,163 @@ while (!done) #ifdef SUPPORT_TLS case 'X': - if (!addr) goto ADDR_MISMATCH; /* Below, in 'A' handler */ - switch (subid) - { - case '1': - addr->cipher = NULL; - addr->peerdn = NULL; - - if (*ptr) - addr->cipher = string_copy(ptr); - while (*ptr++); - if (*ptr) - addr->peerdn = string_copy(ptr); - break; - - case '2': - if (*ptr) - (void) tls_import_cert(ptr, &addr->peercert); - else - addr->peercert = NULL; - break; + if (!addr) goto ADDR_MISMATCH; /* Below, in 'A' handler */ + switch (subid) + { + case '1': + addr->cipher = NULL; + addr->peerdn = NULL; - case '3': - if (*ptr) - (void) tls_import_cert(ptr, &addr->ourcert); - else - addr->ourcert = NULL; - break; + if (*ptr) + addr->cipher = string_copy(ptr); + while (*ptr++); + if (*ptr) + addr->peerdn = string_copy(ptr); + break; + + case '2': + if (*ptr) + (void) tls_import_cert(ptr, &addr->peercert); + else + addr->peercert = NULL; + break; + + case '3': + if (*ptr) + (void) tls_import_cert(ptr, &addr->ourcert); + else + addr->ourcert = NULL; + break; # ifndef DISABLE_OCSP - case '4': - addr->ocsp = OCSP_NOT_REQ; - if (*ptr) - addr->ocsp = *ptr - '0'; - break; + case '4': + addr->ocsp = *ptr ? *ptr - '0' : OCSP_NOT_REQ; + break; # endif - } - while (*ptr++); - break; + } + while (*ptr++); + break; #endif /*SUPPORT_TLS*/ case 'C': /* client authenticator information */ - switch (subid) - { - case '1': - addr->authenticator = (*ptr)? string_copy(ptr) : NULL; - break; - case '2': - addr->auth_id = (*ptr)? string_copy(ptr) : NULL; - break; - case '3': - addr->auth_sndr = (*ptr)? string_copy(ptr) : NULL; - break; - } - while (*ptr++); - break; + switch (subid) + { + case '1': addr->authenticator = *ptr ? string_copy(ptr) : NULL; break; + case '2': addr->auth_id = *ptr ? string_copy(ptr) : NULL; break; + case '3': addr->auth_sndr = *ptr ? string_copy(ptr) : NULL; break; + } + while (*ptr++); + break; #ifndef DISABLE_PRDR case 'P': - addr->flags |= af_prdr_used; - break; + setflag(addr, af_prdr_used); + break; #endif case 'K': - addr->flags |= af_chunking_used; - break; + setflag(addr, af_chunking_used); + break; - case 'D': - if (!addr) goto ADDR_MISMATCH; - memcpy(&(addr->dsn_aware), ptr, sizeof(addr->dsn_aware)); - ptr += sizeof(addr->dsn_aware); - DEBUG(D_deliver) debug_printf("DSN read: addr->dsn_aware = %d\n", addr->dsn_aware); - break; + case 'T': + setflag(addr, af_tcp_fastopen); + break; - case 'A': - if (!addr) - { - ADDR_MISMATCH: - msg = string_sprintf("address count mismatch for data read from pipe " - "for transport process %d for transport %s", pid, - addrlist->transport->driver_name); - done = TRUE; + case 'D': + if (!addr) goto ADDR_MISMATCH; + memcpy(&(addr->dsn_aware), ptr, sizeof(addr->dsn_aware)); + ptr += sizeof(addr->dsn_aware); + DEBUG(D_deliver) debug_printf("DSN read: addr->dsn_aware = %d\n", addr->dsn_aware); break; - } - switch (subid) - { -#ifdef SUPPORT_SOCKS - case '2': /* proxy information; must arrive before A0 and applies to that addr XXX oops*/ - proxy_session = TRUE; /*XXX should this be cleared somewhere? */ - if (*ptr == 0) - ptr++; - else - { - proxy_local_address = string_copy(ptr); - while(*ptr++); - memcpy(&proxy_local_port, ptr, sizeof(proxy_local_port)); - ptr += sizeof(proxy_local_port); - } + case 'A': + if (!addr) + { + ADDR_MISMATCH: + msg = string_sprintf("address count mismatch for data read from pipe " + "for transport process %d for transport %s", pid, + addrlist->transport->driver_name); + done = TRUE; break; -#endif + } -#ifdef EXPERIMENTAL_DSN_INFO - case '1': /* must arrive before A0, and applies to that addr */ - /* Two strings: smtp_greeting and helo_response */ - addr->smtp_greeting = string_copy(ptr); - while(*ptr++); - addr->helo_response = string_copy(ptr); - while(*ptr++); - break; -#endif + switch (subid) + { + #ifdef SUPPORT_SOCKS + case '2': /* proxy information; must arrive before A0 and applies to that addr XXX oops*/ + proxy_session = TRUE; /*XXX should this be cleared somewhere? */ + if (*ptr == 0) + ptr++; + else + { + proxy_local_address = string_copy(ptr); + while(*ptr++); + memcpy(&proxy_local_port, ptr, sizeof(proxy_local_port)); + ptr += sizeof(proxy_local_port); + } + break; + #endif - case '0': - addr->transport_return = *ptr++; - addr->special_action = *ptr++; - memcpy(&(addr->basic_errno), ptr, sizeof(addr->basic_errno)); - ptr += sizeof(addr->basic_errno); - memcpy(&(addr->more_errno), ptr, sizeof(addr->more_errno)); - ptr += sizeof(addr->more_errno); - memcpy(&(addr->flags), ptr, sizeof(addr->flags)); - ptr += sizeof(addr->flags); - addr->message = (*ptr)? string_copy(ptr) : NULL; - while(*ptr++); - addr->user_message = (*ptr)? string_copy(ptr) : NULL; - while(*ptr++); + #ifdef EXPERIMENTAL_DSN_INFO + case '1': /* must arrive before A0, and applies to that addr */ + /* Two strings: smtp_greeting and helo_response */ + addr->smtp_greeting = string_copy(ptr); + while(*ptr++); + addr->helo_response = string_copy(ptr); + while(*ptr++); + break; + #endif + + case '0': + DEBUG(D_deliver) debug_printf("A0 %s tret %d\n", addr->address, *ptr); + addr->transport_return = *ptr++; + addr->special_action = *ptr++; + memcpy(&addr->basic_errno, ptr, sizeof(addr->basic_errno)); + ptr += sizeof(addr->basic_errno); + memcpy(&addr->more_errno, ptr, sizeof(addr->more_errno)); + ptr += sizeof(addr->more_errno); + memcpy(&addr->delivery_usec, ptr, sizeof(addr->delivery_usec)); + ptr += sizeof(addr->delivery_usec); + memcpy(&addr->flags, ptr, sizeof(addr->flags)); + ptr += sizeof(addr->flags); + addr->message = *ptr ? string_copy(ptr) : NULL; + while(*ptr++); + addr->user_message = *ptr ? string_copy(ptr) : NULL; + while(*ptr++); - /* Always two strings for host information, followed by the port number and DNSSEC mark */ + /* Always two strings for host information, followed by the port number and DNSSEC mark */ - if (*ptr != 0) - { - h = store_get(sizeof(host_item)); - h->name = string_copy(ptr); - while (*ptr++); - h->address = string_copy(ptr); - while(*ptr++); - memcpy(&(h->port), ptr, sizeof(h->port)); - ptr += sizeof(h->port); - h->dnssec = *ptr == '2' ? DS_YES - : *ptr == '1' ? DS_NO - : DS_UNK; - ptr++; - addr->host_used = h; - } - else ptr++; + if (*ptr) + { + h = store_get(sizeof(host_item)); + h->name = string_copy(ptr); + while (*ptr++); + h->address = string_copy(ptr); + while(*ptr++); + memcpy(&h->port, ptr, sizeof(h->port)); + ptr += sizeof(h->port); + h->dnssec = *ptr == '2' ? DS_YES + : *ptr == '1' ? DS_NO + : DS_UNK; + ptr++; + addr->host_used = h; + } + else ptr++; - /* Finished with this address */ + /* Finished with this address */ - addr = addr->next; - break; - } - break; + addr = addr->next; + break; + } + break; /* Local interface address/port */ case 'I': - if (*ptr) sending_ip_address = string_copy(ptr); - while (*ptr++) ; - if (*ptr) sending_port = atoi(CS ptr); - while (*ptr++) ; - break; + if (*ptr) sending_ip_address = string_copy(ptr); + while (*ptr++) ; + if (*ptr) sending_port = atoi(CS ptr); + while (*ptr++) ; + break; /* Z marks the logical end of the data. It is followed by '0' if continue_transport was NULL at the end of transporting, otherwise '1'. @@ -3620,23 +3672,23 @@ while (!done) most normal messages it will remain NULL all the time. */ case 'Z': - if (*ptr == '0') - { - continue_transport = NULL; - continue_hostname = NULL; - } - done = TRUE; - DEBUG(D_deliver) debug_printf("Z0%c item read\n", *ptr); - break; + if (*ptr == '0') + { + continue_transport = NULL; + continue_hostname = NULL; + } + done = TRUE; + DEBUG(D_deliver) debug_printf("Z0%c item read\n", *ptr); + break; /* Anything else is a disaster. */ default: - msg = string_sprintf("malformed data (%d) read from pipe for transport " - "process %d for transport %s", ptr[-1], pid, - addr->transport->driver_name); - done = TRUE; - break; + msg = string_sprintf("malformed data (%d) read from pipe for transport " + "process %d for transport %s", ptr[-1], pid, + addr->transport->driver_name); + done = TRUE; + break; } } @@ -3898,14 +3950,12 @@ for (;;) /* Normally we do not repeat this loop */ maxpipe = 0; FD_ZERO(&select_pipes); for (poffset = 0; poffset < remote_max_parallel; poffset++) - { if (parlist[poffset].pid != 0) { int fd = parlist[poffset].fd; FD_SET(fd, &select_pipes); if (fd > maxpipe) maxpipe = fd; } - } /* Stick in a 60-second timeout, just in case. */ @@ -3938,7 +3988,6 @@ for (;;) /* Normally we do not repeat this loop */ { readycount--; if (par_read_pipe(poffset, FALSE)) /* Finished with this pipe */ - { for (;;) /* Loop for signals */ { pid_t endedpid = waitpid(pid, &status, 0); @@ -3948,7 +3997,6 @@ for (;;) /* Normally we do not repeat this loop */ "%d (errno = %d) from waitpid() for process %d", (int)endedpid, errno, (int)pid); } - } } } @@ -4344,7 +4392,7 @@ for (delivery_count = 0; addr_remote; delivery_count++) ) ) && ( !multi_domain || ( ( - !tp->expand_multi_domain || (deliver_set_expansions(next), 1), + (void)(!tp->expand_multi_domain || ((void)deliver_set_expansions(next), 1)), exp_bool(addr, US"transport", next->transport->name, D_transport, US"multi_domain", next->transport->multi_domain, @@ -4436,6 +4484,23 @@ for (delivery_count = 0; addr_remote; delivery_count++) if (tp->setup) (void)((tp->setup)(addr->transport, addr, NULL, uid, gid, NULL)); + /* If we have a connection still open from a verify stage (lazy-close) + treat it as if it is a continued connection (apart from the counter used + for the log line mark). */ + + if (cutthrough.fd >= 0 && cutthrough.callout_hold_only) + { + DEBUG(D_deliver) + debug_printf("lazy-callout-close: have conn still open from verification\n"); + continue_transport = cutthrough.transport; + continue_hostname = string_copy(cutthrough.host.name); + continue_host_address = string_copy(cutthrough.host.address); + continue_sequence = 1; + sending_ip_address = cutthrough.snd_ip; + sending_port = cutthrough.snd_port; + smtp_peer_options = cutthrough.peer_options; + } + /* If this is a run to continue delivery down an already-established channel, check that this set of addresses matches the transport and the channel. If it does not, defer the addresses. If a host list exists, @@ -4446,14 +4511,31 @@ for (delivery_count = 0; addr_remote; delivery_count++) if (continue_transport) { BOOL ok = Ustrcmp(continue_transport, tp->name) == 0; - if (ok && addr->host_list) + + /* If the transport is about to override the host list do not check + it here but take the cost of running the transport process to discover + if the continued_hostname connection is suitable. This is a layering + violation which is unfortunate as it requires we haul in the smtp + include file. */ + + if (ok) { - host_item *h; - ok = FALSE; - for (h = addr->host_list; h; h = h->next) - if (Ustrcmp(h->name, continue_hostname) == 0) -/*XXX should also check port here */ - { ok = TRUE; break; } + smtp_transport_options_block * ob; + + if ( !( Ustrcmp(tp->info->driver_name, "smtp") == 0 + && (ob = (smtp_transport_options_block *)tp->options_block) + && ob->hosts_override && ob->hosts + ) + && addr->host_list + ) + { + host_item * h; + ok = FALSE; + for (h = addr->host_list; h; h = h->next) + if (Ustrcmp(h->name, continue_hostname) == 0) + /*XXX should also check port here */ + { ok = TRUE; break; } + } } /* Addresses not suitable; defer or queue for fallback hosts (which @@ -4461,7 +4543,10 @@ for (delivery_count = 0; addr_remote; delivery_count++) if (!ok) { - DEBUG(D_deliver) debug_printf("not suitable for continue_transport\n"); + DEBUG(D_deliver) debug_printf("not suitable for continue_transport (%s)\n", + Ustrcmp(continue_transport, tp->name) != 0 + ? string_sprintf("tpt %s vs %s", continue_transport, tp->name) + : string_sprintf("no host matching %s", continue_hostname)); if (serialize_key) enq_end(serialize_key); if (addr->fallback_hosts && !fallback) @@ -4492,9 +4577,12 @@ for (delivery_count = 0; addr_remote; delivery_count++) /* Set a flag indicating whether there are further addresses that list the continued host. This tells the transport to leave the channel open, - but not to pass it to another delivery process. */ + but not to pass it to another delivery process. We'd like to do that + for non-continue_transport cases too but the knowlege of which host is + connected to is too hard to manage. Perhaps we need a finer-grain + interface to the transport. */ - for (next = addr_remote; next; next = next->next) + for (next = addr_remote; next && !continue_more; next = next->next) { host_item *h; for (h = next->host_list; h; h = h->next) @@ -4598,7 +4686,7 @@ for (delivery_count = 0; addr_remote; delivery_count++) predictable settings for each delivery process, so do something explicit here rather they rely on the fixed reset in the random number function. */ - random_seed = running_in_test_harness? 42 + 2*delivery_count : 0; + random_seed = running_in_test_harness ? 42 + 2*delivery_count : 0; /* Set close-on-exec on the pipe so that it doesn't get passed on to a new process that may be forked to do another delivery down the same @@ -4713,13 +4801,17 @@ for (delivery_count = 0; addr_remote; delivery_count++) if (!addr->peerdn) *ptr++ = 0; else - { - ptr += sprintf(CS ptr, "%.512s", addr->peerdn); - ptr++; - } + ptr += sprintf(CS ptr, "%.512s", addr->peerdn) + 1; rmt_dlv_checked_write(fd, 'X', '1', big_buffer, ptr - big_buffer); } + else if (continue_proxy_cipher) + { + ptr = big_buffer + sprintf(CS big_buffer, "%.128s", continue_proxy_cipher) + 1; + *ptr++ = 0; + rmt_dlv_checked_write(fd, 'X', '1', big_buffer, ptr - big_buffer); + } + if (addr->peercert) { ptr = big_buffer; @@ -4764,16 +4856,18 @@ for (delivery_count = 0; addr_remote; delivery_count++) } #ifndef DISABLE_PRDR - if (addr->flags & af_prdr_used) + if (testflag(addr, af_prdr_used)) rmt_dlv_checked_write(fd, 'P', '0', NULL, 0); #endif - if (addr->flags & af_chunking_used) + if (testflag(addr, af_chunking_used)) rmt_dlv_checked_write(fd, 'K', '0', NULL, 0); + if (testflag(addr, af_tcp_fastopen)) + rmt_dlv_checked_write(fd, 'T', '0', NULL, 0); + memcpy(big_buffer, &addr->dsn_aware, sizeof(addr->dsn_aware)); rmt_dlv_checked_write(fd, 'D', '0', big_buffer, sizeof(addr->dsn_aware)); - DEBUG(D_deliver) debug_printf("DSN write: addr->dsn_aware = %d\n", addr->dsn_aware); /* Retry information: for most success cases this will be null. */ @@ -4781,9 +4875,9 @@ for (delivery_count = 0; addr_remote; delivery_count++) { sprintf(CS big_buffer, "%c%.500s", r->flags, r->key); ptr = big_buffer + Ustrlen(big_buffer+2) + 3; - memcpy(ptr, &(r->basic_errno), sizeof(r->basic_errno)); + memcpy(ptr, &r->basic_errno, sizeof(r->basic_errno)); ptr += sizeof(r->basic_errno); - memcpy(ptr, &(r->more_errno), sizeof(r->more_errno)); + memcpy(ptr, &r->more_errno, sizeof(r->more_errno)); ptr += sizeof(r->more_errno); if (!r->message) *ptr++ = 0; else { @@ -4832,11 +4926,13 @@ for (delivery_count = 0; addr_remote; delivery_count++) sprintf(CS big_buffer, "%c%c", addr->transport_return, addr->special_action); ptr = big_buffer + 2; - memcpy(ptr, &(addr->basic_errno), sizeof(addr->basic_errno)); + memcpy(ptr, &addr->basic_errno, sizeof(addr->basic_errno)); ptr += sizeof(addr->basic_errno); - memcpy(ptr, &(addr->more_errno), sizeof(addr->more_errno)); + memcpy(ptr, &addr->more_errno, sizeof(addr->more_errno)); ptr += sizeof(addr->more_errno); - memcpy(ptr, &(addr->flags), sizeof(addr->flags)); + memcpy(ptr, &addr->delivery_usec, sizeof(addr->delivery_usec)); + ptr += sizeof(addr->delivery_usec); + memcpy(ptr, &addr->flags, sizeof(addr->flags)); ptr += sizeof(addr->flags); if (!addr->message) *ptr++ = 0; else @@ -4849,7 +4945,7 @@ for (delivery_count = 0; addr_remote; delivery_count++) { ptr += sprintf(CS ptr, "%.256s", addr->host_used->name) + 1; ptr += sprintf(CS ptr, "%.64s", addr->host_used->address) + 1; - memcpy(ptr, &(addr->host_used->port), sizeof(addr->host_used->port)); + memcpy(ptr, &addr->host_used->port, sizeof(addr->host_used->port)); ptr += sizeof(addr->host_used->port); /* DNS lookup status */ @@ -4888,6 +4984,19 @@ for (delivery_count = 0; addr_remote; delivery_count++) (void)close(pfd[pipe_write]); + /* If we have a connection still open from a verify stage (lazy-close) + release its TLS library context (if any) as responsibility was passed to + the delivery child process. */ + + if (cutthrough.fd >= 0 && cutthrough.callout_hold_only) + { +#ifdef SUPPORT_TLS + tls_close(FALSE, FALSE); +#endif + (void) close(cutthrough.fd); + release_cutthrough_connection(US"passed to transport proc"); + } + /* Fork failed; defer with error message */ if (pid < 0) @@ -5031,6 +5140,7 @@ if (percent_hack_domains) address_item *new_parent = store_get(sizeof(address_item)); *new_parent = *addr; addr->parent = new_parent; + new_parent->child_count = 1; addr->address = new_address; addr->unique = string_copy(new_address); addr->domain = deliver_domain; @@ -5511,14 +5621,15 @@ give up; if the message has been around for sufficiently long, remove it. */ if (rc != spool_read_hdrerror) { - received_time = 0; + received_time.tv_sec = received_time.tv_usec = 0; + /*XXX subsec precision?*/ for (i = 0; i < 6; i++) - received_time = received_time * BASE_62 + tab62[id[i] - '0']; + received_time.tv_sec = received_time.tv_sec * BASE_62 + tab62[id[i] - '0']; } /* If we've had this malformed message too long, sling it. */ - if (now - received_time > keep_malformed) + if (now - received_time.tv_sec > keep_malformed) { Uunlink(spool_fname(US"msglog", message_subdir, id, US"")); Uunlink(spool_fname(US"input", message_subdir, id, US"-D")); @@ -5901,9 +6012,9 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT) while (p) { - if (parent->child_count == SHRT_MAX) + if (parent->child_count == USHRT_MAX) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "system filter generated more " - "than %d delivery addresses", SHRT_MAX); + "than %d delivery addresses", USHRT_MAX); parent->child_count++; p->parent = parent; @@ -5913,11 +6024,11 @@ else if (system_filter && process_recipients != RECIP_FAIL_TIMEOUT) uschar *type; p->uid = uid; p->gid = gid; - setflag(p, af_uid_set | - af_gid_set | - af_allow_file | - af_allow_pipe | - af_allow_reply); + setflag(p, af_uid_set); + setflag(p, af_gid_set); + setflag(p, af_allow_file); + setflag(p, af_allow_pipe); + setflag(p, af_allow_reply); /* Find the name of the system filter's appropriate pfr transport */ @@ -6019,9 +6130,7 @@ spool if the message is deferred, and in any case there are casing complications for local addresses. */ if (process_recipients != RECIP_IGNORE) - { for (i = 0; i < recipients_count; i++) - { if (!tree_search(tree_nonrecipients, recipients_list[i].address)) { recipient_item *r = recipients_list + i; @@ -6137,8 +6246,6 @@ if (process_recipients != RECIP_IGNORE) } #endif } - } - } DEBUG(D_deliver) { @@ -6203,10 +6310,8 @@ while (addr_new) /* Loop until all addresses dealt with */ not exist. In both cases, dbm_file is NULL. */ if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE))) - { DEBUG(D_deliver|D_retry|D_route|D_hints_lookup) debug_printf("no retry data available\n"); - } /* Scan the current batch of new addresses, to handle pipes, files and autoreplies, and determine which others are ready for routing. */ @@ -6242,8 +6347,8 @@ while (addr_new) /* Loop until all addresses dealt with */ addr->local_part = addr->address; addr->message = US"filter autoreply generated syntactically invalid recipient"; - setflag(addr, af_ignore_error); - (void)post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0); + addr->prop.ignore_error = TRUE; + (void) post_process_one(addr, FAIL, LOG_MAIN, DTYPE_ROUTER, 0); continue; /* with the next new address */ } @@ -6598,7 +6703,6 @@ while (addr_new) /* Loop until all addresses dealt with */ if ((rc = match_isinlist(addr->domain, (const uschar **)&queue_domains, 0, &domainlist_anchor, addr->domain_cache, MCL_DOMAIN, TRUE, NULL)) != OK) - { if (rc == DEFER) { addr->basic_errno = ERRNO_LISTDEFER; @@ -6610,7 +6714,6 @@ while (addr_new) /* Loop until all addresses dealt with */ addr->next = okaddr; okaddr = addr; } - } else { addr->basic_errno = ERRNO_QUEUE_DOMAIN; @@ -6645,7 +6748,7 @@ while (addr_new) /* Loop until all addresses dealt with */ &addr_succeed, v_none)) == DEFER) retry_add_item(addr, addr->router->retry_use_local_part - ? string_sprintf("R:%s@%s", addr->local_part, addr->domain) + ? string_sprintf("R:%s@%s", addr->local_part, addr->domain) : string_sprintf("R:%s", addr->domain), 0); @@ -6739,15 +6842,14 @@ while (addr_new) /* Loop until all addresses dealt with */ addr2->host_list = addr->host_list; addr2->fallback_hosts = addr->fallback_hosts; addr2->prop.errors_address = addr->prop.errors_address; - copyflag(addr2, addr, af_hide_child | af_local_host_removed); + copyflag(addr2, addr, af_hide_child); + copyflag(addr2, addr, af_local_host_removed); DEBUG(D_deliver|D_route) - { debug_printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" "routing %s\n" "Routing for %s copied from %s\n", addr2->address, addr2->address, addr->address); - } } } } /* Continue with routing the next address. */ @@ -7037,6 +7139,7 @@ phase, to minimize cases of half-done things. */ DEBUG(D_deliver) debug_printf(">>>>>>>>>>>>>>>> deliveries are done >>>>>>>>>>>>>>>>\n"); +cancel_cutthrough_connection(TRUE, US"deliveries are done"); /* Root privilege is no longer needed */ @@ -7141,10 +7244,9 @@ for (addr_dsntmp = addr_succeed; addr_dsntmp; addr_dsntmp = addr_dsntmp->next) ) { /* copy and relink address_item and send report with all of them at once later */ - address_item *addr_next; - addr_next = addr_senddsn; + address_item * addr_next = addr_senddsn; addr_senddsn = store_get(sizeof(address_item)); - memcpy(addr_senddsn, addr_dsntmp, sizeof(address_item)); + *addr_senddsn = *addr_dsntmp; addr_senddsn->next = addr_next; } else @@ -7174,7 +7276,7 @@ if (addr_senddsn) FILE *f = fdopen(fd, "wb"); /* header only as required by RFC. only failure DSN needs to honor RET=FULL */ uschar * bound; - transport_ctx tctx = {0}; + transport_ctx tctx = {{0}}; DEBUG(D_deliver) debug_printf("sending error message to: %s\n", sender_address); @@ -7254,8 +7356,9 @@ if (addr_senddsn) /* Write the original email out */ + tctx.u.fd = fileno(f); tctx.options = topt_add_return_path | topt_no_body; - transport_write_message(fileno(f), &tctx, 0); + transport_write_message(&tctx, 0); fflush(f); fprintf(f,"\n--%s--\n", bound); @@ -7311,19 +7414,18 @@ while (addr_failed) if (sender_address[0] == 0 && !addr_failed->prop.errors_address) { if ( !testflag(addr_failed, af_retry_timedout) - && !testflag(addr_failed, af_ignore_error)) - { + && !addr_failed->prop.ignore_error) log_write(0, LOG_MAIN|LOG_PANIC, "internal error: bounce message " "failure is neither frozen nor ignored (it's been ignored)"); - } - setflag(addr_failed, af_ignore_error); + + addr_failed->prop.ignore_error = TRUE; } /* If the first address on the list has af_ignore_error set, just remove it from the list, throw away any saved message file, log it, and mark the recipient done. */ - if ( testflag(addr_failed, af_ignore_error) + if ( addr_failed->prop.ignore_error || ( addr_failed->dsn_flags & rf_dsnflags && (addr_failed->dsn_flags & rf_notify_failure) != rf_notify_failure ) ) @@ -7711,14 +7813,15 @@ wording. */ transport_filter_argv = NULL; /* Just in case */ return_path = sender_address; /* In case not previously set */ { /* Dummy transport for headers add */ - transport_ctx tctx = {0}; + transport_ctx tctx = {{0}}; transport_instance tb = {0}; + tctx.u.fd = fileno(f); tctx.tblock = &tb; tctx.options = topt; tb.add_headers = dsnnotifyhdr; - transport_write_message(fileno(f), &tctx, 0); + transport_write_message(&tctx, 0); } fflush(f); @@ -7750,7 +7853,7 @@ wording. */ if (rc != 0) { uschar *s = US""; - if (now - received_time < retry_maximum_timeout && !addr_defer) + if (now - received_time.tv_sec < retry_maximum_timeout && !addr_defer) { addr_defer = (address_item *)(+1); deliver_freeze = TRUE; @@ -7836,7 +7939,7 @@ if (!addr_defer) if (LOGGING(queue_time_overall)) log_write(0, LOG_MAIN, "Completed QT=%s", - readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) ); + string_timesince(&received_time)); else log_write(0, LOG_MAIN, "Completed"); @@ -7974,7 +8077,7 @@ else if (addr_defer != (address_item *)(+1)) { int count; int show_time; - int queue_time = time(NULL) - received_time; + int queue_time = time(NULL) - received_time.tv_sec; /* When running in the test harness, there's an option that allows us to fudge this time so as to get repeatability of the tests. Take the first @@ -8032,7 +8135,7 @@ else if (addr_defer != (address_item *)(+1)) FILE *wmf = NULL; FILE *f = fdopen(fd, "wb"); uschar * bound; - transport_ctx tctx = {0}; + transport_ctx tctx = {{0}}; if (warn_message_file) if (!(wmf = Ufopen(warn_message_file, "rb"))) @@ -8179,12 +8282,13 @@ else if (addr_defer != (address_item *)(+1)) fflush(f); /* header only as required by RFC. only failure DSN needs to honor RET=FULL */ + tctx.u.fd = fileno(f); tctx.options = topt_add_return_path | topt_no_body; transport_filter_argv = NULL; /* Just in case */ return_path = sender_address; /* In case not previously set */ /* Write the original email out */ - transport_write_message(fileno(f), &tctx, 0); + transport_write_message(&tctx, 0); fflush(f); fprintf(f,"\n--%s--\n", bound); @@ -8414,6 +8518,72 @@ deliver_datafile = -1; return new_sender_address; } + + +void +delivery_re_exec(int exec_type) +{ +uschar * where; + +if (cutthrough.fd >= 0 && cutthrough.callout_hold_only) + { + int pfd[2], channel_fd = cutthrough.fd, pid; + + smtp_peer_options = cutthrough.peer_options; + continue_sequence = 0; + +#ifdef SUPPORT_TLS + if (cutthrough.is_tls) + { + smtp_peer_options |= OPTION_TLS; + sending_ip_address = cutthrough.snd_ip; + sending_port = cutthrough.snd_port; + + where = US"socketpair"; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pfd) != 0) + goto fail; + + where = US"fork"; + if ((pid = fork()) < 0) + goto fail; + + else if (pid == 0) /* child: fork again to totally dosconnect */ + { + close(pfd[1]); + if ((pid = fork())) + _exit(pid ? EXIT_FAILURE : EXIT_SUCCESS); + smtp_proxy_tls(big_buffer, big_buffer_size, pfd[0], 5*60); + exim_exit(0); + } + + close(pfd[0]); + waitpid(pid, NULL, 0); + (void) close(channel_fd); /* release the client socket */ + channel_fd = pfd[1]; + } +#endif + + transport_do_pass_socket(cutthrough.transport, cutthrough.host.name, + cutthrough.host.address, message_id, channel_fd); + } +else + { + cancel_cutthrough_connection(TRUE, US"non-continued delivery"); + (void) child_exec_exim(exec_type, FALSE, NULL, FALSE, 2, US"-Mc", message_id); + } +return; /* compiler quietening; control does not reach here. */ + +fail: + log_write(0, + LOG_MAIN | (exec_type == CEE_EXEC_EXIT ? LOG_PANIC : LOG_PANIC_DIE), + "delivery re-exec %s failed: %s", where, strerror(errno)); + + /* Get here if exec_type == CEE_EXEC_EXIT. + Note: this must be _exit(), not exit(). */ + + _exit(EX_EXECFAILED); +} + /* vi: aw ai sw=2 */ /* End of deliver.c */