X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=src%2Fsrc%2Fdeliver.c;h=a026ec65a177221402d12f028f07a45405a084cd;hb=ade4247855998c3d344382b9bb2a13ff0c5a8921;hp=3dffe78fef8625911b09a071e33b28a1c882f883;hpb=8523533c08c018ac4b750b0e0fab6cfe611e8a49;p=exim.git diff --git a/src/src/deliver.c b/src/src/deliver.c index 3dffe78fe..a026ec65a 100644 --- a/src/src/deliver.c +++ b/src/src/deliver.c @@ -1,10 +1,10 @@ -/* $Cambridge: exim/src/src/deliver.c,v 1.4 2004/12/16 15:11:47 tom Exp $ */ +/* $Cambridge: exim/src/src/deliver.c,v 1.19 2005/06/22 15:44:37 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2004 */ +/* Copyright (c) University of Cambridge 1995 - 2005 */ /* See the file NOTICE for conditions of use and distribution. */ /* The main code for delivering a message. */ @@ -286,7 +286,7 @@ doesn't always get set automatically. */ if (fd >= 0) { - fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); + (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); if (fchown(fd, exim_uid, exim_gid) < 0) { *error = US"chown"; @@ -729,9 +729,27 @@ else if (driver_type == DTYPE_ROUTER) /* If there's an error message set, ensure that it contains only printing characters - it should, but occasionally things slip in and this at least -stops the log format from getting wrecked. */ +stops the log format from getting wrecked. We also scan the message for an LDAP +expansion item that has a password setting, and flatten the password. This is a +fudge, but I don't know a cleaner way of doing this. (If the item is badly +malformed, it won't ever have gone near LDAP.) */ -if (addr->message != NULL) addr->message = string_printing(addr->message); +if (addr->message != NULL) + { + addr->message = string_printing(addr->message); + if (Ustrstr(addr->message, "failed to expand") != NULL && + (Ustrstr(addr->message, "ldap:") != NULL || + Ustrstr(addr->message, "ldapdn:") != NULL || + Ustrstr(addr->message, "ldapm:") != NULL)) + { + uschar *p = Ustrstr(addr->message, "pass="); + if (p != NULL) + { + p += 5; + while (*p != 0 && !isspace(*p)) *p++ = 'x'; + } + } + } /* If we used a transport that has one of the "return_output" options set, and if it did in fact generate some output, then for return_output we treat the @@ -860,6 +878,11 @@ if (result == OK) if ((log_extra_selector & LX_sender_on_delivery) != 0) s = string_append(s, &size, &ptr, 3, US" F=<", sender_address, US">"); + #ifdef EXPERIMENTAL_SRS + if(addr->p.srs_sender) + s = string_append(s, &size, &ptr, 3, US" SRS=<", addr->p.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 @@ -1431,18 +1454,21 @@ return rc; *************************************************/ /* Check that this base address hasn't previously been delivered to its routed -transport. The check is necessary at delivery time in order to handle homonymic -addresses correctly in cases where the pattern of redirection changes between -delivery attempts (so the unique fields change). Non-homonymic previous -delivery is detected earlier, at routing time (which saves unnecessary -routing). +transport. If it has been delivered, mark it done. The check is necessary at +delivery time in order to handle homonymic addresses correctly in cases where +the pattern of redirection changes between delivery attempts (so the unique +fields change). Non-homonymic previous delivery is detected earlier, at routing +time (which saves unnecessary routing). + +Arguments: + addr the address item + testing TRUE if testing wanted only, without side effects -Argument: the address item Returns: TRUE if previously delivered by the transport */ static BOOL -previously_transported(address_item *addr) +previously_transported(address_item *addr, BOOL testing) { (void)string_format(big_buffer, big_buffer_size, "%s/%s", addr->unique + (testflag(addr, af_homonym)? 3:0), addr->transport->name); @@ -1452,7 +1478,7 @@ if (tree_search(tree_nonrecipients, big_buffer) != 0) DEBUG(D_deliver|D_route|D_transport) debug_printf("%s was previously delivered (%s transport): discarded\n", addr->address, addr->transport->name); - child_done(addr, tod_stamp(tod_log)); + if (!testing) child_done(addr, tod_stamp(tod_log)); return TRUE; } @@ -1516,8 +1542,14 @@ transport_instance *tp = addr->transport; /* Set up the return path from the errors or sender address. If the transport has its own return path setting, expand it and replace the existing value. */ -return_path = (addr->p.errors_address != NULL)? - addr->p.errors_address : sender_address; +if(addr->p.errors_address != NULL) + return_path = addr->p.errors_address; +#ifdef EXPERIMENTAL_SRS +else if(addr->p.srs_sender != NULL) + return_path = addr->p.srs_sender; +#endif +else + return_path = sender_address; if (tp->return_path != NULL) { @@ -1697,7 +1729,7 @@ if ((pid = fork()) == 0) gid/uid. */ close(pfd[pipe_read]); - fcntl(pfd[pipe_write], F_SETFD, fcntl(pfd[pipe_write], F_GETFD) | + (void)fcntl(pfd[pipe_write], F_SETFD, fcntl(pfd[pipe_write], F_GETFD) | FD_CLOEXEC); exim_setugid(uid, gid, use_initgroups, string_sprintf("local delivery to %s <%s> transport=%s", addr->local_part, @@ -2039,7 +2071,7 @@ while (addr_local != NULL) attempts. Non-homonymic previous delivery is detected earlier, at routing time. */ - if (previously_transported(addr)) continue; + if (previously_transported(addr, FALSE)) continue; /* There are weird cases where logging is disabled */ @@ -2080,6 +2112,7 @@ while (addr_local != NULL) same characteristics. These are: same transport + not previously delivered (see comment about 50 lines above) same local part if the transport's configuration contains $local_part same domain if the transport's configuration contains $domain same errors address @@ -2093,6 +2126,7 @@ while (addr_local != NULL) { BOOL ok = tp == next->transport && + !previously_transported(next, TRUE) && (!uses_lp || Ustrcmp(next->local_part, addr->local_part) == 0) && (!uses_dom || Ustrcmp(next->domain, addr->domain) == 0) && same_strings(next->p.errors_address, addr->p.errors_address) && @@ -2556,13 +2590,13 @@ also by optional retry data. Read in large chunks into the big buffer and then scan through, interpreting the data therein. In most cases, only a single read will be necessary. No -individual item will ever be anywhere near 500 bytes in length, so by ensuring -that we read the next chunk when there is less than 500 bytes left in the -non-final chunk, we can assume each item is complete in store before handling -it. Actually, each item is written using a single write(), which is atomic for -small items (less than PIPE_BUF, which seems to be at least 512 in any Unix) so -even if we are reading while the subprocess is still going, we should never -have only a partial item in the buffer. +individual item will ever be anywhere near 2500 bytes in length, so by ensuring +that we read the next chunk when there is less than 2500 bytes left in the +non-final chunk, we can assume each item is complete in the buffer before +handling it. Each item is written using a single write(), which is atomic for +small items (less than PIPE_BUF, which seems to be at least 512 in any Unix and +often bigger) so even if we are reading while the subprocess is still going, we +should never have only a partial item in the buffer. Argument: poffset the offset of the parlist item @@ -2598,7 +2632,9 @@ completed. Each separate item is written to the pipe in a single write(), and as they are all short items, the writes will all be atomic and we should never find -ourselves in the position of having read an incomplete item. */ +ourselves in the position of having read an incomplete item. "Short" in this +case can mean up to about 1K in the case when there is a long error message +associated with an address. */ DEBUG(D_deliver) debug_printf("reading pipe for subprocess %d (%s)\n", (int)p->pid, eop? "ended" : "not ended"); @@ -2612,7 +2648,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 < 500 && unfinished) + if (remaining < 2500 && unfinished) { int len; int available = big_buffer_size - remaining; @@ -3368,7 +3404,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++) attempts. Non-homonymic previous delivery is detected earlier, at routing time. */ - if (previously_transported(addr)) continue; + if (previously_transported(addr, FALSE)) continue; /* Force failure if the message is too big. */ @@ -3503,8 +3539,14 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++) /* Compute the return path, expanding a new one if required. The old one must be set first, as it might be referred to in the expansion. */ - return_path = (addr->p.errors_address != NULL)? - addr->p.errors_address : sender_address; + if(addr->p.errors_address != NULL) + return_path = addr->p.errors_address; +#ifdef EXPERIMENTAL_SRS + else if(addr->p.srs_sender != NULL) + return_path = addr->p.srs_sender; +#endif + else + return_path = sender_address; if (tp->return_path != NULL) { @@ -3631,9 +3673,9 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++) distinguishes between EOF and no-more-data. */ #ifdef O_NONBLOCK - fcntl(pfd[pipe_read], F_SETFL, O_NONBLOCK); + (void)fcntl(pfd[pipe_read], F_SETFL, O_NONBLOCK); #else - fcntl(pfd[pipe_read], F_SETFL, O_NDELAY); + (void)fcntl(pfd[pipe_read], F_SETFL, O_NDELAY); #endif /* If the maximum number of subprocesses already exist, wait for a process @@ -3706,7 +3748,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++) a new process that may be forked to do another delivery down the same SMTP connection. */ - fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); + (void)fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); /* Close open file descriptors for the pipes of other processes that are running in parallel. */ @@ -3733,7 +3775,7 @@ for (delivery_count = 0; addr_remote != NULL; delivery_count++) /* Set the close-on-exec flag */ - fcntl(deliver_datafile, F_SETFD, fcntl(deliver_datafile, F_GETFD) | + (void)fcntl(deliver_datafile, F_SETFD, fcntl(deliver_datafile, F_GETFD) | FD_CLOEXEC); /* Set the uid/gid of this process; bombs out on failure. */ @@ -4155,7 +4197,6 @@ if (addr->parent != NULL && testflag(addr, af_hide_child)) printed = US"an undisclosed address"; yield = FALSE; } - else if (!testflag(addr, af_pfr) || addr->parent == NULL) printed = addr->address; @@ -4192,7 +4233,6 @@ return yield; - /************************************************* * Print error for an address * *************************************************/ @@ -4202,47 +4242,108 @@ a bounce or a warning message. It tries to format the message reasonably by introducing newlines. All lines are indented by 4; the initial printing position must be set before calling. +This function used always to print the error. Nowadays we want to restrict it +to cases such as SMTP errors from a remote host, and errors from :fail: and +filter "fail". We no longer pass other information willy-nilly in bounce and +warning messages. Text in user_message is always output; text in message only +if the af_pass_message flag is set. + Arguments: - addr points to the address + addr the address f the FILE to print on + s some leading text Returns: nothing */ static void -print_address_error(address_item *addr, FILE *f) +print_address_error(address_item *addr, FILE *f, uschar *t) { +int count = Ustrlen(t); uschar *s = (addr->user_message != NULL)? addr->user_message : addr->message; -if (addr->basic_errno > 0) - { - fprintf(f, "%s%s", strerror(addr->basic_errno), - (s == NULL)? "" : ":\n "); - } -if (s == NULL) + +if (addr->user_message != NULL) + s = addr->user_message; +else { - if (addr->basic_errno <= 0) fprintf(f, "unknown error"); + if (!testflag(addr, af_pass_message) || addr->message == NULL) return; + s = addr->message; } -else + +fprintf(f, "\n %s", t); + +while (*s != 0) { - int count = 0; - while (*s != 0) + if (*s == '\\' && s[1] == 'n') { - if (*s == '\\' && s[1] == 'n') + fprintf(f, "\n "); + s += 2; + count = 0; + } + else + { + fputc(*s, f); + count++; + if (*s++ == ':' && isspace(*s) && count > 45) { - fprintf(f, "\n "); - s += 2; + fprintf(f, "\n "); /* sic (because space follows) */ count = 0; } - else - { - fputc(*s, f); - count++; - if (*s++ == ':' && isspace(*s) && count > 45) - { - fprintf(f, "\n "); /* sic (because space follows) */ - count = 0; - } - } + } + } +} + + + + + + +/************************************************* +* Check list of addresses for duplication * +*************************************************/ + +/* This function was introduced when the test for duplicate addresses that are +not pipes, files, or autoreplies was moved from the middle of routing to when +routing was complete. That was to fix obscure cases when the routing history +affects the subsequent routing of identical addresses. If that change has to be +reversed, this function is no longer needed. For a while, the old code that was +affected by this change is commented with !!!OLD-DE-DUP!!! so it can be found +easily. + +This function is called after routing, to check that the final routed addresses +are not duplicates. If we detect a duplicate, we remember what it is a +duplicate of. Note that pipe, file, and autoreply de-duplication is handled +during routing, so we must leave such "addresses" alone here, as otherwise they +will incorrectly be discarded. + +Argument: address of list anchor +Returns: nothing +*/ + +static void +do_duplicate_check(address_item **anchor) +{ +address_item *addr; +while ((addr = *anchor) != NULL) + { + tree_node *tnode; + if (testflag(addr, af_pfr)) + { + anchor = &(addr->next); + } + else if ((tnode = tree_search(tree_duplicates, addr->unique)) != NULL) + { + DEBUG(D_deliver|D_route) + debug_printf("%s is a duplicate address: discarded\n", addr->unique); + *anchor = addr->next; + addr->dupof = tnode->data.ptr; + addr->next = addr_duplicate; + addr_duplicate = addr; + } + else + { + tree_add_duplicate(addr->unique, addr); + anchor = &(addr->next); } } } @@ -4381,11 +4482,8 @@ if ((rc = spool_read_header(spoolname, TRUE, TRUE)) != spool_read_OK) sprintf(CS big_buffer, "%s/input/%s/%s", spool_directory, message_subdir, spoolname); if (Ustat(big_buffer, &statbuf) == 0) - { - int size = statbuf.st_size; /* Because might be a long */ - log_write(0, LOG_MAIN, "Format error in spool file %s: size=%d", - spoolname, size); - } + log_write(0, LOG_MAIN, "Format error in spool file %s: " + "size=" OFF_T_FMT, spoolname, statbuf.st_size); else log_write(0, LOG_MAIN, "Format error in spool file %s", spoolname); } else @@ -4503,17 +4601,22 @@ if (deliver_freeze) log_write(0, LOG_MAIN, "Unfrozen by errmsg timer"); } - /* If there's no auto thaw, or we haven't reached the auto thaw time yet, and - this delivery is not forced by an admin user, do not attempt delivery of this - message. Note that forced is set for continuing messages down the same - channel, in order to skip load checking and ignore hold domains, but we - don't want unfreezing in that case. */ + /* If this is a bounce message, or there's no auto thaw, or we haven't + reached the auto thaw time yet, and this delivery is not forced by an admin + user, do not attempt delivery of this message. Note that forced is set for + continuing messages down the same channel, in order to skip load checking and + ignore hold domains, but we don't want unfreezing in that case. */ else { - if ((auto_thaw <= 0 || now <= deliver_frozen_at + auto_thaw) && - (!forced || !deliver_force_thaw || !admin_user || - continue_hostname != NULL)) + if ((sender_address[0] == 0 || + auto_thaw <= 0 || + now <= deliver_frozen_at + auto_thaw + ) + && + (!forced || !deliver_force_thaw || !admin_user || + continue_hostname != NULL + )) { close(deliver_datafile); deliver_datafile = -1; @@ -4637,6 +4740,8 @@ else if (system_filter != NULL && process_recipients != RECIP_FAIL_TIMEOUT) RDO_REWRITE, NULL, /* No :include: restriction (not used in filter) */ NULL, /* No sieve vacation directory (not sieve!) */ + NULL, /* No sieve user address (not sieve!) */ + NULL, /* No sieve subaddress (not sieve!) */ &ugid, /* uid/gid data */ &addr_new, /* Where to hang generated addresses */ &filter_message, /* Where to put error message */ @@ -4915,6 +5020,7 @@ if (process_recipients != RECIP_IGNORE) case RECIP_FAIL_FILTER: new->message = (filter_message == NULL)? US"delivery cancelled" : filter_message; + setflag(new, af_pass_message); goto RECIP_QUEUE_FAILED; /* below */ @@ -5263,6 +5369,18 @@ while (addr_new != NULL) /* Loop until all addresses dealt with */ continue; } + + /* !!!OLD-DE-DUP!!! We used to test for duplicates at this point, in order + to save effort on routing duplicate addresses. However, facilities have + been added to Exim so that now two identical addresses that are children of + other addresses may be routed differently as a result of their previous + routing history. For example, different redirect routers may have given + them different redirect_router values, but there are other cases too. + Therefore, tests for duplicates now take place when routing is complete. + This is the old code, kept for a while for the record, and in case this + radical change has to be backed out for some reason. */ + + #ifdef NEVER /* If it's a duplicate, remember what it's a duplicate of */ if ((tnode = tree_search(tree_duplicates, addr->unique)) != NULL) @@ -5278,6 +5396,9 @@ while (addr_new != NULL) /* Loop until all addresses dealt with */ /* Record this address, so subsequent duplicates get picked up. */ tree_add_duplicate(addr->unique, addr); + #endif + + /* Get the routing retry status, saving the two retry keys (with and without the local part) for subsequent use. Ignore retry records that @@ -5590,6 +5711,21 @@ Ensure they are not set in transports. */ local_user_gid = (gid_t)(-1); local_user_uid = (uid_t)(-1); + +/* !!!OLD-DE-DUP!!! The next two statement were introduced when checking for +duplicates was moved from within routing to afterwards. If that change has to +be backed out, they should be removed. */ + +/* Check for any duplicate addresses. This check is delayed until after +routing, because the flexibility of the routing configuration means that +identical addresses with different parentage may end up being redirected to +different addresses. Checking for duplicates too early (as we previously used +to) makes this kind of thing not work. */ + +do_duplicate_check(&addr_local); +do_duplicate_check(&addr_remote); + + /* When acting as an MUA wrapper, we proceed only if all addresses route to a remote transport. The check that they all end up in one transaction happens in the do_remote_deliveries() function. */ @@ -5709,9 +5845,9 @@ if (addr_local != NULL || addr_remote != NULL) that the mode is correct - the group setting doesn't always seem to get set automatically. */ - fcntl(journal_fd, F_SETFD, fcntl(journal_fd, F_GETFD) | FD_CLOEXEC); - fchown(journal_fd, exim_uid, exim_gid); - fchmod(journal_fd, SPOOL_MODE); + (void)fcntl(journal_fd, F_SETFD, fcntl(journal_fd, F_GETFD) | FD_CLOEXEC); + (void)fchown(journal_fd, exim_uid, exim_gid); + (void)fchmod(journal_fd, SPOOL_MODE); } @@ -6073,26 +6209,15 @@ wording. */ /* 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 - post_process_one() for the existence of data in the message file.) */ + post_process_one() for the existence of data in the message file.) A TRUE + return from print_address_information() means that the address is not + hidden. */ paddr = &msgchain; for (addr = msgchain; addr != NULL; addr = *paddr) { if (print_address_information(addr, f, US" ", US"\n ", US"")) - { - /* A TRUE return from print_address_information() means that the - address is not hidden. If there is a return file, it has already - been checked to ensure it is not empty. Omit the bland "return - message generated" error, but otherwise include error information. */ - - if (addr->return_file < 0 || - addr->message == NULL || - Ustrcmp(addr->message, "return message generated") != 0) - { - fprintf(f, "\n "); - print_address_error(addr, f); - } - } + print_address_error(addr, f, US""); /* End the final line for the address */ @@ -6220,8 +6345,8 @@ wording. */ if (emf_text != NULL) fprintf(f, "%s", CS emf_text); else { fprintf(f, -"------ The body of the message is %d characters long; only the first\n" -"------ %d or so are included here.\n", (int)statbuf.st_size, max); +"------ 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); } } } @@ -6335,7 +6460,7 @@ if (addr_defer == NULL) } /* Remove the two message files. */ - + sprintf(CS spoolname, "%s/input/%s/%s-D", spool_directory, message_subdir, id); if (Uunlink(spoolname) < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "failed to unlink %s", spoolname); @@ -6346,7 +6471,7 @@ if (addr_defer == NULL) /* Log the end of this message, with queue time if requested. */ if ((log_extra_selector & LX_queue_time_overall) != 0) - log_write(0, LOG_MAIN, "Completed QT=%s", + log_write(0, LOG_MAIN, "Completed QT=%s", readconf_printtime(time(NULL) - received_time)); else log_write(0, LOG_MAIN, "Completed"); @@ -6594,21 +6719,15 @@ else if (addr_defer != (address_item *)(+1)) (addr_defer->next == NULL)? "is": "are"); } - /* List the addresses. For any that are hidden, don't give the delay - reason, because it might expose that which is hidden. Also, do not give - "retry time not reached" because that isn't helpful. */ + /* List the addresses, with error information if allowed */ fprintf(f, "\n"); while (addr_defer != NULL) { address_item *addr = addr_defer; addr_defer = addr->next; - if (print_address_information(addr, f, US" ", US"\n ", US"") && - addr->basic_errno > ERRNO_RETRY_BASE) - { - fprintf(f, "\n Delay reason: "); - print_address_error(addr, f); - } + if (print_address_information(addr, f, US" ", US"\n ", US"")) + print_address_error(addr, f, US"Delay reason: "); fprintf(f, "\n"); } fprintf(f, "\n");