X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/9bdd29ad5c5d394229753b114f6f288893f616f4..a7538db17824b7fd70c12ef7561a67b85d6f247e:/src/src/deliver.c diff --git a/src/src/deliver.c b/src/src/deliver.c index 8e1d17793..48d3fd7ec 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2009 */ +/* Copyright (c) University of Cambridge 1995 - 2014 */ /* See the file NOTICE for conditions of use and distribution. */ /* The main code for delivering a message. */ @@ -63,6 +63,10 @@ static address_item *addr_new = NULL; static address_item *addr_remote = NULL; static address_item *addr_route = NULL; static address_item *addr_succeed = NULL; +#ifdef EXPERIMENTAL_DSN +static address_item *addr_dsntmp = NULL; +static address_item *addr_senddsn = NULL; +#endif static FILE *message_log = NULL; static BOOL update_spool; @@ -137,11 +141,13 @@ the first address. */ if (addr->host_list == NULL) { deliver_host = deliver_host_address = US""; + deliver_host_port = 0; } else { deliver_host = addr->host_list->name; deliver_host_address = addr->host_list->address; + deliver_host_port = addr->host_list->port; } deliver_recipients = addr; @@ -673,8 +679,73 @@ while (addr->parent != NULL) +static uschar * +d_hostlog(uschar * s, int * sizep, int * ptrp, address_item * addr) +{ + s = string_append(s, sizep, ptrp, 5, US" H=", addr->host_used->name, + US" [", addr->host_used->address, US"]"); + if ((log_extra_selector & LX_outgoing_port) != 0) + s = string_append(s, sizep, ptrp, 2, US":", string_sprintf("%d", + addr->host_used->port)); + return s; +} + +#ifdef SUPPORT_TLS +static uschar * +d_tlslog(uschar * s, int * sizep, int * ptrp, address_item * addr) +{ + if ((log_extra_selector & LX_tls_cipher) != 0 && addr->cipher != NULL) + s = string_append(s, sizep, ptrp, 2, US" X=", addr->cipher); + if ((log_extra_selector & LX_tls_certificate_verified) != 0 && + addr->cipher != NULL) + s = string_append(s, sizep, ptrp, 2, US" CV=", + testflag(addr, af_cert_verified)? "yes":"no"); + if ((log_extra_selector & LX_tls_peerdn) != 0 && addr->peerdn != NULL) + s = string_append(s, sizep, ptrp, 3, US" DN=\"", + string_printing(addr->peerdn), US"\""); + return s; +} +#endif + + +#ifdef EXPERIMENTAL_TPDA +int +tpda_raise_event(uschar * action, uschar * event, uschar * ev_data) +{ +uschar * s; +if (action) + { + DEBUG(D_deliver) + debug_printf("TPDA(%s): tpda_event_action=|%s| tpda_delivery_IP=%s\n", + event, + action, deliver_host_address); + + tpda_event = event; + tpda_data = ev_data; + + if (!(s = expand_string(action)) && *expand_string_message) + log_write(0, LOG_MAIN|LOG_PANIC, + "failed to expand tpda_event_action %s in %s: %s\n", + event, transport_name, expand_string_message); + + tpda_event = tpda_data = NULL; + + /* If the expansion returns anything but an empty string, flag for + the caller to modify his normal processing + */ + if (s && *s) + { + DEBUG(D_deliver) + debug_printf("TPDA(%s): event_action returned \"%s\"\n", s); + return DEFER; + } + } +return OK; +} +#endif + /* If msg is NULL this is a delivery log and logchar is used. Otherwise -this is a nonstandard call; no two-characher delivery flag is written +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: @@ -696,12 +767,8 @@ 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. */ #ifdef EXPERIMENTAL_TPDA - tpda_delivery_ip = NULL; /* presume no successful remote delivery */ - tpda_delivery_port = 0; - tpda_delivery_fqdn = NULL; - tpda_delivery_local_part = NULL; - tpda_delivery_domain = NULL; - tpda_delivery_confirmation = NULL; + /* presume no successful remote delivery */ + lookup_dnssec_authenticated = NULL; #endif s = reset_point = store_get(size); @@ -749,13 +816,8 @@ if ((log_extra_selector & LX_delivery_size) != 0) if (addr->transport->info->local) { - if (addr->host_list != NULL) - { + if (addr->host_list) s = string_append(s, &size, &ptr, 2, US" H=", addr->host_list->name); - #ifdef EXPERIMENTAL_TPDA - tpda_delivery_fqdn = addr->host_list->name; - #endif - } if (addr->shadow_message != NULL) s = string_cat(s, &size, &ptr, addr->shadow_message, Ustrlen(addr->shadow_message)); @@ -765,37 +827,26 @@ if (addr->transport->info->local) else { - if (addr->host_used != NULL) + if (addr->host_used) { - s = string_append(s, &size, &ptr, 5, US" H=", addr->host_used->name, - US" [", addr->host_used->address, US"]"); - if ((log_extra_selector & LX_outgoing_port) != 0) - s = string_append(s, &size, &ptr, 2, US":", string_sprintf("%d", - addr->host_used->port)); + s = d_hostlog(s, &size, &ptr, addr); if (continue_sequence > 1) s = string_cat(s, &size, &ptr, US"*", 1); - #ifdef EXPERIMENTAL_TPDA - tpda_delivery_ip = addr->host_used->address; - tpda_delivery_port = addr->host_used->port; - tpda_delivery_fqdn = addr->host_used->name; - tpda_delivery_local_part = addr->local_part; - tpda_delivery_domain = addr->domain; - tpda_delivery_confirmation = addr->message; - #endif +#ifdef EXPERIMENTAL_TPDA + deliver_host_address = addr->host_used->address; + deliver_host_port = addr->host_used->port; + + /* 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 - if ((log_extra_selector & LX_tls_cipher) != 0 && addr->cipher != NULL) - s = string_append(s, &size, &ptr, 2, US" X=", addr->cipher); - if ((log_extra_selector & LX_tls_certificate_verified) != 0 && - addr->cipher != NULL) - s = string_append(s, &size, &ptr, 2, US" CV=", - testflag(addr, af_cert_verified)? "yes":"no"); - if ((log_extra_selector & LX_tls_peerdn) != 0 && addr->peerdn != NULL) - s = string_append(s, &size, &ptr, 3, US" DN=\"", - string_printing(addr->peerdn), US"\""); - #endif +#ifdef SUPPORT_TLS + s = d_tlslog(s, &size, &ptr, addr); +#endif if (addr->authenticator) { @@ -808,42 +859,41 @@ else } } - #ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR if (addr->flags & af_prdr_used) s = string_append(s, &size, &ptr, 1, US" PRDR"); - #endif +#endif + } - if ((log_extra_selector & LX_smtp_confirmation) != 0 && - addr->message != NULL) - { - int i; - uschar *p = big_buffer; - uschar *ss = addr->message; - *p++ = '\"'; - for (i = 0; i < 100 && ss[i] != 0; i++) - { - if (ss[i] == '\"' || ss[i] == '\\') *p++ = '\\'; - *p++ = ss[i]; - } - *p++ = '\"'; - *p = 0; - s = string_append(s, &size, &ptr, 2, US" C=", big_buffer); - } +/* confirmation message (SMTP (host_used) and LMTP (driver_name)) */ + +if (log_extra_selector & LX_smtp_confirmation && + addr->message && + (addr->host_used || Ustrcmp(addr->transport->driver_name, "lmtp") == 0)) + { + int i; + uschar *p = big_buffer; + uschar *ss = addr->message; + *p++ = '\"'; + for (i = 0; i < 256 && 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 ((log_extra_selector & LX_queue_time) != 0) - { s = string_append(s, &size, &ptr, 2, US" QT=", - readconf_printtime(time(NULL) - received_time)); - } + readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) ); if ((log_extra_selector & LX_deliver_time) != 0) - { s = string_append(s, &size, &ptr, 2, US" DT=", readconf_printtime(addr->more_errno)); - } /* string_cat() always leaves room for the terminator. Release the store we used to build the line after writing it. */ @@ -852,19 +902,22 @@ s[ptr] = 0; log_write(0, flags, "%s", s); #ifdef EXPERIMENTAL_TPDA -if (addr->transport->tpda_delivery_action) { - DEBUG(D_deliver) - debug_printf(" TPDA(Delivery): tpda_deliver_action=|%s| tpda_delivery_IP=%s\n", - addr->transport->tpda_delivery_action, tpda_delivery_ip); - - router_name = addr->router->name; - transport_name = addr->transport->name; - if (!expand_string(addr->transport->tpda_delivery_action) && *expand_string_message) - log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand tpda_deliver_action in %s: %s\n", - transport_name, expand_string_message); - router_name = NULL; - transport_name = NULL; + uschar * save_domain = deliver_domain; + uschar * save_local = deliver_localpart; + + router_name = addr->router ? addr->router->name : NULL; + transport_name = addr->transport ? addr->transport->name : NULL; + deliver_domain = addr->domain; + deliver_localpart = addr->local_part; + + (void) tpda_raise_event(addr->transport->tpda_event_action, US"msg:delivery", + addr->host_used || Ustrcmp(addr->transport->driver_name, "lmtp") == 0 + ? addr->message : NULL); + + deliver_localpart = save_local; + deliver_domain = save_domain; + router_name = transport_name = NULL; } #endif store_reset(reset_point); @@ -1038,7 +1091,7 @@ if (addr->return_file >= 0 && addr->return_filename != NULL) (void)close(addr->return_file); } -/* The sucess case happens only after delivery by a transport. */ +/* The success case happens only after delivery by a transport. */ if (result == OK) { @@ -1054,10 +1107,8 @@ if (result == OK) DEBUG(D_deliver) debug_printf("%s delivered\n", addr->address); if (addr->parent == NULL) - { deliver_msglog("%s %s: %s%s succeeded\n", now, addr->address, driver_name, driver_kind); - } else { deliver_msglog("%s %s <%s>: %s%s succeeded\n", now, addr->address, @@ -1065,7 +1116,35 @@ if (result == OK) child_done(addr, now); } + /* Certificates for logging (via TPDA) */ +#ifdef SUPPORT_TLS + tls_out.ourcert = addr->ourcert; + addr->ourcert = NULL; + tls_out.peercert = addr->peercert; + addr->peercert = NULL; + + tls_out.cipher = addr->cipher; + tls_out.peerdn = addr->peerdn; + tls_out.ocsp = addr->ocsp; +#endif + delivery_log(LOG_MAIN, addr, logchar, NULL); + +#ifdef SUPPORT_TLS + if (tls_out.ourcert) + { + tls_free_cert(tls_out.ourcert); + tls_out.ourcert = NULL; + } + if (tls_out.peercert) + { + tls_free_cert(tls_out.peercert); + tls_out.peercert = NULL; + } + tls_out.cipher = NULL; + tls_out.peerdn = NULL; + tls_out.ocsp = OCSP_NOT_REQ; +#endif } @@ -1236,9 +1315,7 @@ else if (used_return_path != NULL && (log_extra_selector & LX_return_path_on_delivery) != 0) - { s = string_append(s, &size, &ptr, 3, US" P=<", used_return_path, US">"); - } if (addr->router != NULL) s = string_append(s, &size, &ptr, 2, US" R=", addr->router->name); @@ -1246,8 +1323,11 @@ else s = string_append(s, &size, &ptr, 2, US" T=", addr->transport->name); if (addr->host_used != NULL) - s = string_append(s, &size, &ptr, 5, US" H=", addr->host_used->name, - US" [", addr->host_used->address, US"]"); + s = d_hostlog(s, &size, &ptr, addr); + +#ifdef SUPPORT_TLS + s = d_tlslog(s, &size, &ptr, addr); +#endif if (addr->basic_errno > 0) s = string_append(s, &size, &ptr, 2, US": ", @@ -1836,19 +1916,19 @@ if ((pid = fork()) == 0) diagnosis that it's reasonable to make them something that has to be explicitly requested. */ - #ifdef RLIMIT_CORE +#ifdef RLIMIT_CORE struct rlimit rl; rl.rlim_cur = 0; rl.rlim_max = 0; if (setrlimit(RLIMIT_CORE, &rl) < 0) { - #ifdef SETRLIMIT_NOT_SUPPORTED +# ifdef SETRLIMIT_NOT_SUPPORTED if (errno != ENOSYS && errno != ENOTSUP) - #endif +# endif log_write(0, LOG_MAIN|LOG_PANIC, "setrlimit(RLIMIT_CORE) failed: %s", strerror(errno)); } - #endif +#endif /* Reset the random number generator, so different processes don't all have the same sequence. */ @@ -2935,35 +3015,75 @@ while (!done) it in with the other info, in order to keep each message short enough to guarantee it won't be split in the pipe. */ - #ifdef SUPPORT_TLS +#ifdef SUPPORT_TLS case 'X': - if (addr == NULL) goto ADDR_MISMATCH; /* Below, in 'A' handler */ - addr->cipher = (*ptr)? string_copy(ptr) : NULL; - while (*ptr++); - addr->peerdn = (*ptr)? string_copy(ptr) : NULL; + if (addr == NULL) goto ADDR_MISMATCH; /* Below, in 'A' handler */ + switch (*ptr++) + { + 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': + addr->peercert = NULL; + if (*ptr) + (void) tls_import_cert(ptr, &addr->peercert); + break; + + case '3': + addr->ourcert = NULL; + if (*ptr) + (void) tls_import_cert(ptr, &addr->ourcert); + break; + +# ifndef DISABLE_OCSP + case '4': + addr->ocsp = OCSP_NOT_REQ; + if (*ptr) + addr->ocsp = *ptr - '0'; + break; +# endif + } while (*ptr++); break; - #endif +#endif /*SUPPORT_TLS*/ case 'C': /* client authenticator information */ switch (*ptr++) - { - 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; - } + { + 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; -#ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR case 'P': - addr->flags |= af_prdr_used; break; + addr->flags |= af_prdr_used; + break; +#endif + +#ifdef EXPERIMENTAL_DSN + case 'D': + if (addr == NULL) break; + 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; #endif case 'A': @@ -2990,7 +3110,7 @@ while (!done) addr->user_message = (*ptr)? string_copy(ptr) : NULL; while(*ptr++); - /* Always two strings for host information, followed by the port number */ + /* Always two strings for host information, followed by the port number and DNSSEC mark */ if (*ptr != 0) { @@ -3001,6 +3121,10 @@ while (!done) 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++; @@ -3858,11 +3982,11 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++) that it can use either of them, though it prefers O_NONBLOCK, which distinguishes between EOF and no-more-data. */ - #ifdef O_NONBLOCK +#ifdef O_NONBLOCK (void)fcntl(pfd[pipe_read], F_SETFL, O_NONBLOCK); - #else +#else (void)fcntl(pfd[pipe_read], F_SETFL, O_NDELAY); - #endif +#endif /* If the maximum number of subprocesses already exist, wait for a process to finish. If we ran out of file descriptors, parmax will have been reduced @@ -4028,25 +4152,55 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++) retry_item *r; /* The certificate verification status goes into the flags */ - if (tls_out.certificate_verified) setflag(addr, af_cert_verified); /* Use an X item only if there's something to send */ - - #ifdef SUPPORT_TLS - if (addr->cipher != NULL) +#ifdef SUPPORT_TLS + if (addr->cipher) { ptr = big_buffer; - sprintf(CS ptr, "X%.128s", addr->cipher); + sprintf(CS ptr, "X1%.128s", addr->cipher); while(*ptr++); - if (addr->peerdn == NULL) *ptr++ = 0; else + if (!addr->peerdn) + *ptr++ = 0; + else { sprintf(CS ptr, "%.512s", addr->peerdn); while(*ptr++); } + rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer); } - #endif + if (addr->peercert) + { + ptr = big_buffer; + *ptr++ = 'X'; *ptr++ = '2'; + if (!tls_export_cert(ptr, big_buffer_size-2, addr->peercert)) + while(*ptr++); + else + *ptr++ = 0; + rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer); + } + if (addr->ourcert) + { + ptr = big_buffer; + *ptr++ = 'X'; *ptr++ = '3'; + if (!tls_export_cert(ptr, big_buffer_size-2, addr->ourcert)) + while(*ptr++); + else + *ptr++ = 0; + rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer); + } +# ifndef DISABLE_OCSP + if (addr->ocsp > OCSP_NOT_REQ) + { + ptr = big_buffer; + sprintf(CS ptr, "X4%c", addr->ocsp + '0'); + while(*ptr++); + rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer); + } +# endif +#endif /*SUPPORT_TLS*/ if (client_authenticator) { @@ -4070,9 +4224,17 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++) rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer); } - #ifdef EXPERIMENTAL_PRDR - if (addr->flags & af_prdr_used) rmt_dlv_checked_write(fd, "P", 1); - #endif +#ifndef DISABLE_PRDR + if (addr->flags & af_prdr_used) + rmt_dlv_checked_write(fd, "P", 1); +#endif + +#ifdef EXPERIMENTAL_DSN + big_buffer[0] = 'D'; + memcpy(big_buffer+1, &addr->dsn_aware, sizeof(addr->dsn_aware)); + rmt_dlv_checked_write(fd, big_buffer, sizeof(addr->dsn_aware) + 1); + DEBUG(D_deliver) debug_printf("DSN write: addr->dsn_aware = %d\n", addr->dsn_aware); +#endif /* Retry information: for most success cases this will be null. */ @@ -4125,6 +4287,11 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++) while(*ptr++); memcpy(ptr, &(addr->host_used->port), sizeof(addr->host_used->port)); ptr += sizeof(addr->host_used->port); + + /* DNS lookup status */ + *ptr++ = addr->host_used->dnssec==DS_YES ? '2' + : addr->host_used->dnssec==DS_NO ? '1' : '0'; + } rmt_dlv_checked_write(fd, big_buffer, ptr - big_buffer); } @@ -4782,7 +4949,7 @@ attempted. */ if (deliver_freeze) { - #ifdef SUPPORT_MOVE_FROZEN_MESSAGES +#ifdef SUPPORT_MOVE_FROZEN_MESSAGES /* Moving to another directory removes the message from Exim's view. Other tools must be used to deal with it. Logging of this action happens in spool_move_message() and its subfunctions. */ @@ -4790,7 +4957,7 @@ if (deliver_freeze) if (move_frozen_messages && spool_move_message(id, message_subdir, US"", US"F")) return continue_closedown(); /* yields DELIVER_NOT_ATTEMPTED */ - #endif +#endif /* For all frozen messages (bounces or not), timeout_frozen_after sets the maximum time to keep messages that are frozen. Thaw if we reach it, with a @@ -5219,6 +5386,14 @@ if (process_recipients != RECIP_IGNORE) if (r->pno >= 0) new->onetime_parent = recipients_list[r->pno].address; +#ifdef EXPERIMENTAL_DSN + /* If DSN support is enabled, set the dsn flags and the original receipt + to be passed on to other DSN enabled MTAs */ + new->dsn_flags = r->dsn_flags & rf_dsnflags; + new->dsn_orcpt = r->orcpt; + DEBUG(D_deliver) debug_printf("DSN: set orcpt: %s flags: %d\n", new->dsn_orcpt, new->dsn_flags); +#endif + switch (process_recipients) { /* RECIP_DEFER is set when a system filter freezes a message. */ @@ -6153,15 +6328,21 @@ if (addr_remote != NULL) regex_must_compile(US"\\n250[\\s\\-]AUTH\\s+([\\-\\w\\s]+)(?:\\n|$)", FALSE, TRUE); - #ifdef SUPPORT_TLS +#ifdef SUPPORT_TLS if (regex_STARTTLS == NULL) regex_STARTTLS = regex_must_compile(US"\\n250[\\s\\-]STARTTLS(\\s|\\n|$)", FALSE, TRUE); - #endif +#endif - #ifdef EXPERIMENTAL_PRDR +#ifndef DISABLE_PRDR if (regex_PRDR == NULL) regex_PRDR = regex_must_compile(US"\\n250[\\s\\-]PRDR(\\s|\\n|$)", FALSE, TRUE); - #endif +#endif + +#ifdef EXPERIMENTAL_DSN + /* Set the regex to check for DSN support on remote MTA */ + if (regex_DSN == NULL) regex_DSN = + regex_must_compile(US"\\n250[\\s\\-]DSN(\\s|\\n|$)", FALSE, TRUE); +#endif /* Now sort the addresses if required, and do the deliveries. The yield of do_remote_deliveries is FALSE when mua_wrapper is set and all addresses @@ -6267,6 +6448,169 @@ prevents actual delivery. */ else if (!dont_deliver) retry_update(&addr_defer, &addr_failed, &addr_succeed); +#ifdef EXPERIMENTAL_DSN +/* Send DSN for successful messages */ +addr_dsntmp = addr_succeed; +addr_senddsn = NULL; + +while(addr_dsntmp != NULL) + { + DEBUG(D_deliver) + debug_printf("DSN: processing router : %s\n", addr_dsntmp->router->name); + + DEBUG(D_deliver) + debug_printf("DSN: processing successful delivery address: %s\n", addr_dsntmp->address); + + /* af_ignore_error not honored here. it's not an error */ + + DEBUG(D_deliver) debug_printf("DSN: Sender_address: %s\n", sender_address); + DEBUG(D_deliver) debug_printf("DSN: orcpt: %s flags: %d\n", addr_dsntmp->dsn_orcpt, addr_dsntmp->dsn_flags); + DEBUG(D_deliver) debug_printf("DSN: envid: %s ret: %d\n", dsn_envid, dsn_ret); + DEBUG(D_deliver) debug_printf("DSN: Final recipient: %s\n", addr_dsntmp->address); + DEBUG(D_deliver) debug_printf("DSN: Remote SMTP server supports DSN: %d\n", addr_dsntmp->dsn_aware); + + /* send report if next hop not DSN aware or a router flagged "last DSN hop" + and a report was requested */ + if (((addr_dsntmp->dsn_aware != dsn_support_yes) || + ((addr_dsntmp->dsn_flags & rf_dsnlasthop) != 0)) + && + (((addr_dsntmp->dsn_flags & rf_dsnflags) != 0) && + ((addr_dsntmp->dsn_flags & rf_notify_success) != 0))) + { + /* copy and relink address_item and send report with all of them at once later */ + address_item *addr_next; + addr_next = addr_senddsn; + addr_senddsn = store_get(sizeof(address_item)); + memcpy(addr_senddsn, addr_dsntmp, sizeof(address_item)); + addr_senddsn->next = addr_next; + } + else + { + DEBUG(D_deliver) debug_printf("DSN: *** NOT SENDING DSN SUCCESS Message ***\n"); + } + + addr_dsntmp = addr_dsntmp->next; + } + +if (addr_senddsn != NULL) + { + pid_t pid; + int fd; + + /* create exim process to send message */ + pid = child_open_exim(&fd); + + DEBUG(D_deliver) debug_printf("DSN: child_open_exim returns: %d\n", pid); + + if (pid < 0) /* Creation of child failed */ + { + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Process %d (parent %d) failed to " + "create child process to send failure message: %s", getpid(), + getppid(), strerror(errno)); + + DEBUG(D_deliver) debug_printf("DSN: child_open_exim failed\n"); + + } + else /* Creation of child succeeded */ + { + FILE *f = fdopen(fd, "wb"); + /* header only as required by RFC. only failure DSN needs to honor RET=FULL */ + int topt = topt_add_return_path | topt_no_body; + uschar boundaryStr[64]; + + DEBUG(D_deliver) debug_printf("sending error message to: %s\n", sender_address); + + /* build unique id for MIME boundary */ + snprintf(boundaryStr, sizeof(boundaryStr)-1, TIME_T_FMT "-eximdsn-%d", + time(NULL), rand()); + DEBUG(D_deliver) debug_printf("DSN: MIME boundary: %s\n", boundaryStr); + + if (errors_reply_to) + fprintf(f, "Reply-To: %s\n", errors_reply_to); + + fprintf(f, "Auto-Submitted: auto-generated\n" + "From: Mail Delivery System \n" + "To: %s\n" + "Subject: Delivery Status Notification\n" + "Content-Type: multipart/report; report-type=delivery-status; boundary=%s\n" + "MIME-Version: 1.0\n\n" + + "--%s\n" + "Content-type: text/plain; charset=us-ascii\n\n" + + "This message was created automatically by mail delivery software.\n" + " ----- The following addresses had successful delivery notifications -----\n", + qualify_domain_sender, sender_address, boundaryStr, boundaryStr); + + addr_dsntmp = addr_senddsn; + while(addr_dsntmp) + { + fprintf(f, "<%s> (relayed %s)\n\n", + addr_dsntmp->address, + (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1 + ? "via non DSN router" + : addr_dsntmp->dsn_aware == dsn_support_no + ? "to non-DSN-aware mailer" + : "via non \"Remote SMTP\" router" + ); + addr_dsntmp = addr_dsntmp->next; + } + fprintf(f, "--%s\n" + "Content-type: message/delivery-status\n\n" + "Reporting-MTA: dns; %s\n", + boundaryStr, smtp_active_hostname); + + if (dsn_envid != NULL) { + /* must be decoded from xtext: see RFC 3461:6.3a */ + uschar *xdec_envid; + if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0) + fprintf(f, "Original-Envelope-ID: %s\n", dsn_envid); + else + fprintf(f, "X-Original-Envelope-ID: error decoding xtext formated ENVID\n"); + } + fputc('\n', f); + + for (addr_dsntmp = addr_senddsn; + addr_dsntmp; + addr_dsntmp = addr_dsntmp->next) + { + if (addr_dsntmp->dsn_orcpt) + fprintf(f,"Original-Recipient: %s\n", addr_dsntmp->dsn_orcpt); + + fprintf(f, "Action: delivered\n" + "Final-Recipient: rfc822;%s\n" + "Status: 2.0.0\n", + addr_dsntmp->address); + + if (addr_dsntmp->host_used && addr_dsntmp->host_used->name) + fprintf(f, "Remote-MTA: dns; %s\nDiagnostic-Code: smtp; 250 Ok\n", + addr_dsntmp->host_used->name); + else + fprintf(f,"Diagnostic-Code: X-Exim; relayed via non %s router\n", + (addr_dsntmp->dsn_flags & rf_dsnlasthop) == 1 ? "DSN" : "SMTP"); + fputc('\n', f); + } + + fprintf(f, "--%s\nContent-type: text/rfc822-headers\n\n", boundaryStr); + + fflush(f); + transport_filter_argv = NULL; /* Just in case */ + return_path = sender_address; /* In case not previously set */ + + /* Write the original email out */ + transport_write_message(NULL, fileno(f), topt, 0, NULL, NULL, NULL, NULL, NULL, 0); + fflush(f); + + fprintf(f,"\n"); + fprintf(f,"--%s--\n", boundaryStr); + + fflush(f); + fclose(f); + rc = child_close(pid, 0); /* Waits for child to close, no timeout */ + } + } +#endif /*EXPERIMENTAL_DSN*/ + /* If any addresses failed, we must send a message to somebody, unless af_ignore_error is set, in which case no action is taken. It is possible for several messages to get sent if there are addresses with different @@ -6324,8 +6668,13 @@ while (addr_failed != NULL) 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 (testflag(addr_failed, af_ignore_error) +#ifdef EXPERIMENTAL_DSN + || (((addr_failed->dsn_flags & rf_dsnflags) != 0) + && ((addr_failed->dsn_flags & rf_notify_failure) != rf_notify_failure)) +#endif + ) + { addr = addr_failed; addr_failed = addr->next; if (addr->return_filename != NULL) Uunlink(addr->return_filename); @@ -6377,6 +6726,12 @@ while (addr_failed != NULL) BOOL to_sender = strcmpic(sender_address, bounce_recipient) == 0; int max = (bounce_return_size_limit/DELIVER_IN_BUFFER_SIZE + 1) * DELIVER_IN_BUFFER_SIZE; +#ifdef EXPERIMENTAL_DSN + uschar boundaryStr[64]; + uschar *dsnlimitmsg; + uschar *dsnnotifyhdr; + int topt; +#endif DEBUG(D_deliver) debug_printf("sending error message to: %s\n", bounce_recipient); @@ -6430,57 +6785,70 @@ while (addr_failed != NULL) moan_write_from(f); fprintf(f, "To: %s\n", bounce_recipient); +#ifdef EXPERIMENTAL_DSN + /* generate boundary string and output MIME-Headers */ + snprintf(boundaryStr, sizeof(boundaryStr)-1, TIME_T_FMT "-eximdsn-%d", + time(NULL), rand()); + + fprintf(f, "Content-Type: multipart/report;" + " report-type=delivery-status; boundary=%s\n" + "MIME-Version: 1.0\n", + boundaryStr); +#endif + /* Open a template file if one is provided. Log failure to open, but carry on - default texts will be used. */ - if (bounce_message_file != NULL) - { - emf = Ufopen(bounce_message_file, "rb"); - if (emf == NULL) + if (bounce_message_file) + if (!(emf = Ufopen(bounce_message_file, "rb"))) log_write(0, LOG_MAIN|LOG_PANIC, "Failed to open %s for error " "message texts: %s", bounce_message_file, strerror(errno)); - } /* Quietly copy to configured additional addresses if required. */ - bcc = moan_check_errorcopy(bounce_recipient); - if (bcc != NULL) fprintf(f, "Bcc: %s\n", bcc); + if ((bcc = moan_check_errorcopy(bounce_recipient))) + fprintf(f, "Bcc: %s\n", bcc); /* The texts for the message can be read from a template file; if there isn't one, or if it is too short, built-in texts are used. The first emf text is a Subject: and any other headers. */ - emf_text = next_emf(emf, US"header"); - if (emf_text != NULL) fprintf(f, "%s\n", emf_text); else - { + if ((emf_text = next_emf(emf, US"header"))) + fprintf(f, "%s\n", emf_text); + else fprintf(f, "Subject: Mail delivery failed%s\n\n", to_sender? ": returning message to sender" : ""); - } - emf_text = next_emf(emf, US"intro"); - if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else +#ifdef EXPERIMENTAL_DSN + /* output human readable part as text/plain section */ + fprintf(f, "--%s\n" + "Content-type: text/plain; charset=us-ascii\n\n", + boundaryStr); +#endif + + if ((emf_text = next_emf(emf, US"intro"))) + fprintf(f, "%s", CS emf_text); + else { fprintf(f, /* This message has been reworded several times. It seems to be confusing to somebody, however it is worded. I have retreated to the original, simple wording. */ "This message was created automatically by mail delivery software.\n"); - if (bounce_message_text != NULL) fprintf(f, "%s", CS bounce_message_text); + + if (bounce_message_text) + fprintf(f, "%s", CS bounce_message_text); if (to_sender) - { fprintf(f, "\nA message that you sent could not be delivered to one or more of its\n" "recipients. This is a permanent error. The following address(es) failed:\n"); - } else - { fprintf(f, "\nA message sent by\n\n <%s>\n\n" "could not be delivered to one or more of its recipients. The following\n" "address(es) failed:\n", sender_address); - } } - fprintf(f, "\n"); + fputc('\n', f); /* Process the addresses, leaving them on the msgchain if they have a file name for a return message. (There has already been a check in @@ -6517,7 +6885,7 @@ wording. */ } } - fprintf(f, "\n"); + fputc('\n', f); /* Get the next text, whether we need it or not, so as to be positioned for the one after. */ @@ -6531,11 +6899,13 @@ wording. */ fd, and the return_filename field in the *last* one will be set (to the name of the file). */ - if (msgchain != NULL) + if (msgchain) { address_item *nextaddr; - if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else + if (emf_text) + fprintf(f, "%s", CS emf_text); + else fprintf(f, "The following text was generated during the delivery " "attempt%s:\n", (filecount > 1)? "s" : ""); @@ -6547,15 +6917,15 @@ wording. */ /* List all the addresses that relate to this file */ - fprintf(f, "\n"); - while(addr != NULL) /* Insurance */ + fputc('\n', f); + while(addr) /* Insurance */ { print_address_information(addr, f, US"------ ", US"\n ", US" ------\n"); - if (addr->return_filename != NULL) break; + if (addr->return_filename) break; addr = addr->next; } - fprintf(f, "\n"); + fputc('\n', f); /* Now copy the file */ @@ -6578,8 +6948,38 @@ wording. */ addr->next = handled_addr; handled_addr = topaddr; } - fprintf(f, "\n"); + fputc('\n', f); + } + +#ifdef EXPERIMENTAL_DSN + /* output machine readable part */ + fprintf(f, "--%s\n" + "Content-type: message/delivery-status\n\n" + "Reporting-MTA: dns; %s\n", + boundaryStr, smtp_active_hostname); + + if (dsn_envid) + { + /* must be decoded from xtext: see RFC 3461:6.3a */ + uschar *xdec_envid; + if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0) + fprintf(f, "Original-Envelope-ID: %s\n", dsn_envid); + else + fprintf(f, "X-Original-Envelope-ID: error decoding xtext formated ENVID\n"); + } + fputc('\n', f); + + for (addr = handled_addr; addr; addr = addr->next) + { + fprintf(f, "Action: failed\n" + "Final-Recipient: rfc822;%s\n" + "Status: 5.0.0\n", + addr->address); + if (addr->host_used && addr->host_used->name) + fprintf(f, "Remote-MTA: dns; %s\nDiagnostic-Code: smtp; %d\n", + addr->host_used->name, addr->basic_errno); } +#endif /* Now copy the message, trying to give an intelligible comment if it is too long for it all to be copied. The limit isn't strictly @@ -6588,12 +6988,15 @@ wording. */ emf_text = next_emf(emf, US"copy"); +#ifndef EXPERIMENTAL_DSN if (bounce_return_message) { int topt = topt_add_return_path; if (!bounce_return_body) topt |= topt_no_body; - if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else + if (emf_text) + fprintf(f, "%s", CS emf_text); + else { if (bounce_return_body) fprintf(f, "------ This is a copy of the message, including all the headers. ------\n"); @@ -6616,18 +7019,17 @@ wording. */ { struct stat statbuf; if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max) - { - if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else - { + if (emf_text) + fprintf(f, "%s", CS emf_text); + else fprintf(f, "------ The body of the message is " OFF_T_FMT " characters long; only the first\n" "------ %d or so are included here.\n", statbuf.st_size, max); - } - } } - fprintf(f, "\n"); + fputc('\n', f); fflush(f); + transport_filter_argv = NULL; /* Just in case */ return_path = sender_address; /* In case not previously set */ transport_write_message(NULL, fileno(f), topt, @@ -6636,12 +7038,71 @@ wording. */ /* Write final text and close the template file if one is open */ - if (emf != NULL) + if (emf) { - emf_text = next_emf(emf, US"final"); - if (emf_text != NULL) fprintf(f, "%s", CS emf_text); + if ((emf_text = next_emf(emf, US"final"))) + fprintf(f, "%s", CS emf_text); (void)fclose(emf); } +#else + /* add message body + we ignore the intro text from template and add + the text for bounce_return_size_limit at the end. + + bounce_return_message is ignored + in case RET= is defined we honor these values + otherwise bounce_return_body is honored. + + bounce_return_size_limit is always honored. + */ + + fprintf(f, "\n--%s\n", boundaryStr); + + dsnlimitmsg = US"X-Exim-DSN-Information: Due to administrative limits only headers are returned"; + dsnnotifyhdr = NULL; + topt = topt_add_return_path; + + /* RET=HDRS? top priority */ + if (dsn_ret == dsn_ret_hdrs) + topt |= topt_no_body; + else + /* no full body return at all? */ + if (!bounce_return_body) + { + topt |= topt_no_body; + /* add header if we overrule RET=FULL */ + if (dsn_ret == dsn_ret_full) + dsnnotifyhdr = dsnlimitmsg; + } + /* size limited ... return headers only if limit reached */ + else if (bounce_return_size_limit > 0) + { + struct stat statbuf; + if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max) + { + topt |= topt_no_body; + dsnnotifyhdr = dsnlimitmsg; + } + } + + if (topt & topt_no_body) + fprintf(f,"Content-type: text/rfc822-headers\n\n"); + else + fprintf(f,"Content-type: message/rfc822\n\n"); + + fflush(f); + transport_filter_argv = NULL; /* Just in case */ + return_path = sender_address; /* In case not previously set */ + transport_write_message(NULL, fileno(f), topt, + 0, dsnnotifyhdr, NULL, NULL, NULL, NULL, 0); + fflush(f); + + /* we never add the final text. close the file */ + if (emf) + (void)fclose(emf); + + fprintf(f, "\n--%s--\n", boundaryStr); +#endif /*EXPERIMENTAL_DSN*/ /* Close the file, which should send an EOF to the child process that is receiving the message. Wait for it to finish. */ @@ -6750,7 +7211,7 @@ if (addr_defer == NULL) if ((log_extra_selector & LX_queue_time_overall) != 0) log_write(0, LOG_MAIN, "Completed QT=%s", - readconf_printtime(time(NULL) - received_time)); + readconf_printtime( (int) ((long)time(NULL) - (long)received_time)) ); else log_write(0, LOG_MAIN, "Completed"); @@ -6873,6 +7334,10 @@ else if (addr_defer != (address_item *)(+1)) it also defers). */ if (!queue_2stage && delivery_attempted && +#ifdef EXPERIMENTAL_DSN + (((addr_defer->dsn_flags & rf_dsnflags) == 0) || + (addr_defer->dsn_flags & rf_notify_delay) == rf_notify_delay) && +#endif delay_warning[1] > 0 && sender_address[0] != 0 && (delay_warning_condition == NULL || expand_check_condition(delay_warning_condition, @@ -6937,8 +7402,11 @@ else if (addr_defer != (address_item *)(+1)) uschar *wmf_text; FILE *wmf = NULL; FILE *f = fdopen(fd, "wb"); +#ifdef EXPERIMENTAL_DSN + uschar boundaryStr[64]; +#endif - if (warn_message_file != NULL) + if (warn_message_file) { wmf = Ufopen(warn_message_file, "rb"); if (wmf == NULL) @@ -6951,21 +7419,39 @@ else if (addr_defer != (address_item *)(+1)) string_sprintf("%d minutes", show_time/60): string_sprintf("%d hours", show_time/3600); - if (errors_reply_to != NULL) + if (errors_reply_to) fprintf(f, "Reply-To: %s\n", errors_reply_to); fprintf(f, "Auto-Submitted: auto-replied\n"); moan_write_from(f); fprintf(f, "To: %s\n", recipients); - wmf_text = next_emf(wmf, US"header"); - if (wmf_text != NULL) +#ifdef EXPERIMENTAL_DSN + /* generated boundary string and output MIME-Headers */ + snprintf(boundaryStr, sizeof(boundaryStr)-1, + TIME_T_FMT "-eximdsn-%d", time(NULL), rand()); + + fprintf(f, "Content-Type: multipart/report;" + " report-type=delivery-status; boundary=%s\n" + "MIME-Version: 1.0\n", + boundaryStr); +#endif + + if ((wmf_text = next_emf(wmf, US"header"))) fprintf(f, "%s\n", wmf_text); else fprintf(f, "Subject: Warning: message %s delayed %s\n\n", message_id, warnmsg_delay); - wmf_text = next_emf(wmf, US"intro"); - if (wmf_text != NULL) fprintf(f, "%s", CS wmf_text); else +#ifdef EXPERIMENTAL_DSN + /* output human readable part as text/plain section */ + fprintf(f, "--%s\n" + "Content-type: text/plain; charset=us-ascii\n\n", + boundaryStr); +#endif + + if ((wmf_text = next_emf(wmf, US"intro"))) + fprintf(f, "%s", CS wmf_text); + else { fprintf(f, "This message was created automatically by mail delivery software.\n"); @@ -6975,49 +7461,52 @@ else if (addr_defer != (address_item *)(+1)) "A message that you sent has not yet been delivered to one or more of its\n" "recipients after more than "); - else fprintf(f, + else + fprintf(f, "A message sent by\n\n <%s>\n\n" "has not yet been delivered to one or more of its recipients after more than \n", - sender_address); + sender_address); - fprintf(f, "%s on the queue on %s.\n\n", warnmsg_delay, - primary_hostname); - fprintf(f, "The message identifier is: %s\n", message_id); + fprintf(f, "%s on the queue on %s.\n\n" + "The message identifier is: %s\n", + warnmsg_delay, primary_hostname, message_id); for (h = header_list; h != NULL; h = h->next) - { if (strncmpic(h->text, US"Subject:", 8) == 0) fprintf(f, "The subject of the message is: %s", h->text + 9); else if (strncmpic(h->text, US"Date:", 5) == 0) fprintf(f, "The date of the message is: %s", h->text + 6); - } - fprintf(f, "\n"); + fputc('\n', f); fprintf(f, "The address%s to which the message has not yet been " "delivered %s:\n", - (addr_defer->next == NULL)? "" : "es", - (addr_defer->next == NULL)? "is": "are"); + !addr_defer->next ? "" : "es", + !addr_defer->next ? "is": "are"); } /* List the addresses, with error information if allowed */ - fprintf(f, "\n"); - while (addr_defer != NULL) +#ifdef EXPERIMENTAL_DSN + /* store addr_defer for machine readable part */ + address_item *addr_dsndefer = addr_defer; +#endif + fputc('\n', f); + while (addr_defer) { address_item *addr = addr_defer; addr_defer = addr->next; if (print_address_information(addr, f, US" ", US"\n ", US"")) print_address_error(addr, f, US"Delay reason: "); - fprintf(f, "\n"); + fputc('\n', f); } - fprintf(f, "\n"); + fputc('\n', f); /* Final text */ - if (wmf != NULL) + if (wmf) { - wmf_text = next_emf(wmf, US"final"); - if (wmf_text != NULL) fprintf(f, "%s", CS wmf_text); + if ((wmf_text = next_emf(wmf, US"final"))) + fprintf(f, "%s", CS wmf_text); (void)fclose(wmf); } else @@ -7029,6 +7518,58 @@ else if (addr_defer != (address_item *)(+1)) "and when that happens, the message will be returned to you.\n"); } +#ifdef EXPERIMENTAL_DSN + /* output machine readable part */ + fprintf(f, "\n--%s\n" + "Content-type: message/delivery-status\n\n" + "Reporting-MTA: dns; %s\n", + boundaryStr, + smtp_active_hostname); + + + if (dsn_envid) + { + /* must be decoded from xtext: see RFC 3461:6.3a */ + uschar *xdec_envid; + if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0) + fprintf(f,"Original-Envelope-ID: %s\n", dsn_envid); + else + fprintf(f,"X-Original-Envelope-ID: error decoding xtext formated ENVID\n"); + } + fputc('\n', f); + + while (addr_dsndefer) + { + if (addr_dsndefer->dsn_orcpt) + fprintf(f,"Original-Recipient: %s\n", addr_dsndefer->dsn_orcpt); + + fprintf(f,"Action: delayed\n"); + fprintf(f,"Final-Recipient: rfc822;%s\n", addr_dsndefer->address); + fprintf(f,"Status: 4.0.0\n"); + if (addr_dsndefer->host_used && addr_dsndefer->host_used->name) + fprintf(f,"Remote-MTA: dns; %s\nDiagnostic-Code: smtp; %d\n", + addr_dsndefer->host_used->name, addr_dsndefer->basic_errno); + addr_dsndefer = addr_dsndefer->next; + } + + fprintf(f, "\n--%s\n" + "Content-type: text/rfc822-headers\n\n", + boundaryStr); + + fflush(f); + /* header only as required by RFC. only failure DSN needs to honor RET=FULL */ + int topt = 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(NULL, fileno(f), topt, 0, NULL, NULL, NULL, NULL, NULL, 0); + fflush(f); + + fprintf(f,"\n--%s--\n", boundaryStr); + + fflush(f); +#endif /*EXPERIMENTAL_DSN*/ + /* Close and wait for child process to complete, without a timeout. If there's an error, don't update the count. */ @@ -7140,10 +7681,10 @@ if (remove_journal) /* Move the message off the spool if reqested */ - #ifdef SUPPORT_MOVE_FROZEN_MESSAGES +#ifdef SUPPORT_MOVE_FROZEN_MESSAGES if (deliver_freeze && move_frozen_messages) (void)spool_move_message(id, message_subdir, US"", US"F"); - #endif +#endif } /* Closing the data file frees the lock; if the file has been unlinked it @@ -7165,4 +7706,6 @@ acl_where = ACL_WHERE_UNKNOWN; return final_yield; } +/* vi: aw ai sw=2 +*/ /* End of deliver.c */