X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=src%2Fsrc%2Fretry.c;h=364591bd09214a617794d557fde67d7471cb0dca;hb=a98e6acae2a0aa1ba884da4d6cfb4c107b053e25;hp=b54bb7d4a428d62a304db01b599793867eb3739c;hpb=d7d7b7b91dd75cec636fc144da7e27eed860f971;p=exim.git diff --git a/src/src/retry.c b/src/src/retry.c index b54bb7d4a..364591bd0 100644 --- a/src/src/retry.c +++ b/src/src/retry.c @@ -1,10 +1,8 @@ -/* $Cambridge: exim/src/src/retry.c,v 1.5 2006/02/07 11:19:00 ph10 Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2006 */ +/* Copyright (c) University of Cambridge 1995 - 2015 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions concerned with retrying unsuccessful deliveries. */ @@ -19,26 +17,34 @@ *************************************************/ /* This function tests whether a message has been on the queue longer than -the maximum retry time for a particular host. +the maximum retry time for a particular host or address. Arguments: - host_key the key to look up a host retry rule + retry_key the key to look up a retry rule domain the domain to look up a domain retry rule - basic_errno a specific error number, or zero if none - more_errno additional data for the error + retry_record contains error information for finding rule now the time Returns: TRUE if the ultimate timeout has been reached */ -static BOOL -ultimate_address_timeout(uschar *host_key, uschar *domain, int basic_errno, - int more_errno, time_t now) +BOOL +retry_ultimate_address_timeout(uschar *retry_key, const uschar *domain, + dbdata_retry *retry_record, time_t now) { -BOOL address_timeout = TRUE; /* no rule => timed out */ +BOOL address_timeout; + +DEBUG(D_retry) + { + debug_printf("retry time not reached: checking ultimate address timeout\n"); + debug_printf(" now=%d first_failed=%d next_try=%d expired=%d\n", + (int)now, (int)retry_record->first_failed, + (int)retry_record->next_try, retry_record->expired); + } retry_config *retry = - retry_find_config(host_key+2, domain, basic_errno, more_errno); + retry_find_config(retry_key+2, domain, + retry_record->basic_errno, retry_record->more_errno); if (retry != NULL && retry->rules != NULL) { @@ -46,8 +52,22 @@ if (retry != NULL && retry->rules != NULL) for (last_rule = retry->rules; last_rule->next != NULL; last_rule = last_rule->next); + DEBUG(D_retry) + debug_printf(" received_time=%d diff=%d timeout=%d\n", + received_time, (int)(now - received_time), last_rule->timeout); address_timeout = (now - received_time > last_rule->timeout); } +else + { + DEBUG(D_retry) + debug_printf("no retry rule found: assume timed out\n"); + address_timeout = TRUE; + } + +DEBUG(D_retry) + if (address_timeout) + debug_printf("on queue longer than maximum retry for address - " + "allowing delivery\n"); return address_timeout; } @@ -102,7 +122,7 @@ Returns: TRUE if the host has expired but is usable because */ BOOL -retry_check_address(uschar *domain, host_item *host, uschar *portstring, +retry_check_address(const uschar *domain, host_item *host, uschar *portstring, BOOL include_ip_address, uschar **retry_host_key, uschar **retry_message_key) { BOOL yield = FALSE; @@ -200,19 +220,10 @@ if (host_retry_record != NULL) if (now < host_retry_record->next_try && !deliver_force) { - DEBUG(D_transport|D_retry) - debug_printf("host retry time not reached: checking ultimate address " - "timeout\n"); - if (!host_retry_record->expired && - ultimate_address_timeout(host_key, domain, - host_retry_record->basic_errno, host_retry_record->more_errno, now)) - { - DEBUG(D_transport|D_retry) - debug_printf("on queue longer than maximum retry for " - "address - allowing delivery\n"); + retry_ultimate_address_timeout(host_key, domain, + host_retry_record, now)) return FALSE; - } /* We have not hit the ultimate address timeout; host is unusable. */ @@ -237,20 +248,12 @@ if (message_retry_record != NULL) *retry_message_key = message_key; if (now < message_retry_record->next_try && !deliver_force) { - DEBUG(D_transport|D_retry) - debug_printf("host+message retry time not reached: checking ultimate " - "address timeout\n"); - if (!ultimate_address_timeout(host_key, domain, 0, 0, now)) + if (!retry_ultimate_address_timeout(host_key, domain, + message_retry_record, now)) { host->status = hstatus_unusable; host->why = hwhy_retry; } - else - { - DEBUG(D_transport|D_retry) - debug_printf("on queue longer than maximum retry for " - "address - allowing delivery\n"); - } return FALSE; } } @@ -291,12 +294,15 @@ void retry_add_item(address_item *addr, uschar *key, int flags) { retry_item *rti = store_get(sizeof(retry_item)); +host_item * host = addr->host_used; rti->next = addr->retries; addr->retries = rti; rti->key = key; rti->basic_errno = addr->basic_errno; rti->more_errno = addr->more_errno; -rti->message = addr->message; +rti->message = host + ? string_sprintf("H=%s [%s]: %s", host->name, host->address, addr->message) + : addr->message; rti->flags = flags; DEBUG(D_transport|D_retry) @@ -337,39 +343,45 @@ Returns: pointer to retry rule, or NULL */ retry_config * -retry_find_config(uschar *key, uschar *alternate, int basic_errno, +retry_find_config(const uschar *key, const uschar *alternate, int basic_errno, int more_errno) { -int replace; -uschar *use_key, *use_alternate; -uschar *colon = Ustrchr(key, ':'); +const uschar *colon = Ustrchr(key, ':'); retry_config *yield; -/* If there's a colon in the key, temporarily replace it with -a zero to terminate the string there. */ +/* If there's a colon in the key, there are two possibilities: -if (colon != NULL) - { - replace = ':'; - } -else - { - colon = key + Ustrlen(key); - replace = 0; - } -*colon = 0; +(1) This is a key for a host, ip address, and possibly port, in the format + + hostname:ip+port + + In this case, we copy the host name. + +(2) This is a key for a pipe, file, or autoreply delivery, in the format + + pipe-or-file-or-auto:x@y + + where x@y is the original address that provoked the delivery. The pipe or + file or auto will start with | or / or >, whereas a host name will start + with a letter or a digit. In this case we want to use the original address + to search for a retry rule. */ + +if (colon) + key = isalnum(*key) + ? string_copyn(key, colon-key) /* the hostname */ + : Ustrrchr(key, ':') + 1; /* Take from the last colon */ /* Sort out the keys */ -use_key = (Ustrchr(key, '@') != NULL)? key : string_sprintf("*@%s", key); -use_alternate = (alternate == NULL)? NULL : string_sprintf("*@%s", alternate); +if (!Ustrchr(key, '@')) key = string_sprintf("*@%s", key); +if (alternate) alternate = string_sprintf("*@%s", alternate); /* Scan the configured retry items. */ for (yield = retries; yield != NULL; yield = yield->next) { - uschar *plist = yield->pattern; - uschar *slist = yield->senders; + const uschar *plist = yield->pattern; + const uschar *slist = yield->senders; /* If a specific error is set for this item, check that we are handling that specific error, and if so, check any additional error information if @@ -390,20 +402,31 @@ for (yield = retries; yield != NULL; yield = yield->next) continue; } - /* Handle 4xx responses to RCPT. The code that was received is in the 2nd - least significant byte of more_errno (with 400 subtracted). The required - value is coded in the 2nd least significant byte of the yield->more_errno - field as follows: + /* The TLSREQUIRED error also covers TLSFAILURE. These are subtly different + errors, but not worth separating at this level. */ + + else if (yield->basic_errno == ERRNO_TLSREQUIRED) + { + if (basic_errno != ERRNO_TLSREQUIRED && basic_errno != ERRNO_TLSFAILURE) + continue; + } + + /* Handle 4xx responses to MAIL, RCPT, or DATA. The code that was received + is in the 2nd least significant byte of more_errno (with 400 subtracted). + The required value is coded in the 2nd least significant byte of the + yield->more_errno field as follows: 255 => any 4xx code >= 100 => the decade must match the value less 100 < 100 => the exact value must match */ - else if (yield->basic_errno == ERRNO_RCPT4XX) + else if (yield->basic_errno == ERRNO_MAIL4XX || + yield->basic_errno == ERRNO_RCPT4XX || + yield->basic_errno == ERRNO_DATA4XX) { int wanted; - if (basic_errno != ERRNO_RCPT4XX) continue; + if (basic_errno != yield->basic_errno) continue; wanted = (yield->more_errno >> 8) & 255; if (wanted != 255) { @@ -457,15 +480,14 @@ for (yield = retries; yield != NULL; yield = yield->next) /* Check for a match between the address list item at the start of this retry rule and either the main or alternate keys. */ - if (match_address_list(use_key, TRUE, TRUE, &plist, NULL, -1, UCHAR_MAX+1, + if (match_address_list(key, TRUE, TRUE, &plist, NULL, -1, UCHAR_MAX+1, NULL) == OK || - (use_alternate != NULL && - match_address_list(use_alternate, TRUE, TRUE, &plist, NULL, -1, + (alternate != NULL && + match_address_list(alternate, TRUE, TRUE, &plist, NULL, -1, UCHAR_MAX+1, NULL) == OK)) break; } -*colon = replace; return yield; } @@ -514,12 +536,12 @@ for (i = 0; i < 3; i++) { address_item *endaddr, *addr; address_item *last_first = NULL; - address_item **paddr = (i==0)? addr_succeed : - (i==1)? addr_failed : addr_defer; + address_item **paddr = i==0 ? addr_succeed : + i==1 ? addr_failed : addr_defer; address_item **saved_paddr = NULL; - DEBUG(D_retry) debug_printf("%s addresses:\n", (i == 0)? "Succeeded" : - (i == 1)? "Failed" : "Deferred"); + DEBUG(D_retry) debug_printf("%s addresses:\n", + i == 0 ? "Succeeded" : i == 1 ? "Failed" : "Deferred"); /* Loop for each address on the chain. For deferred addresses, the whole address times out unless one of its retry addresses has a retry rule that @@ -531,22 +553,22 @@ for (i = 0; i < 3; i++) retry items for any parent addresses - these are typically "delete" items, because the parent must have succeeded in order to generate the child. */ - while ((endaddr = *paddr) != NULL) + while ((endaddr = *paddr)) { BOOL timed_out = FALSE; retry_item *rti; - for (addr = endaddr; addr != NULL; addr = addr->parent) + for (addr = endaddr; addr; addr = addr->parent) { int update_count = 0; int timedout_count = 0; - DEBUG(D_retry) debug_printf("%s%s\n", addr->address, (addr->retries == NULL)? - ": no retry items" : ""); + DEBUG(D_retry) debug_printf(" %s%s\n", addr->address, + addr->retries ? "" : ": no retry items"); /* Loop for each retry item. */ - for (rti = addr->retries; rti != NULL; rti = rti->next) + for (rti = addr->retries; rti; rti = rti->next) { uschar *message; int message_length, message_space, failing_interval, next_try; @@ -560,10 +582,10 @@ for (i = 0; i < 3; i++) opening if no addresses have retry items - common when none have yet reached their retry next try time. */ - if (dbm_file == NULL) + if (!dbm_file) dbm_file = dbfn_open(US"retry", O_RDWR, &dbblock, TRUE); - if (dbm_file == NULL) + if (!dbm_file) { DEBUG(D_deliver|D_retry|D_hints_lookup) debug_printf("retry database not available for updating\n"); @@ -578,13 +600,13 @@ for (i = 0; i < 3; i++) but the address gets delivered to the second one. This optimization doesn't succeed in cleaning out all the dead entries, but it helps. */ - if (*addr_defer == NULL && (rti->flags & rf_message) != 0) + if (!*addr_defer && rti->flags & rf_message) rti->flags |= rf_delete; /* Handle the case of a request to delete the retry info for this destination. */ - if ((rti->flags & rf_delete) != 0) + if (rti->flags & rf_delete) { (void)dbfn_delete(dbm_file, rti->key); DEBUG(D_retry) @@ -604,34 +626,38 @@ for (i = 0; i < 3; i++) information is found, we can't generate a retry time, so there is no point updating the database. This retry item is timed out. */ - if ((retry = retry_find_config(rti->key + 2, - ((rti->flags & rf_host) != 0)? addr->domain : NULL, - rti->basic_errno, rti->more_errno)) == NULL) + if (!(retry = retry_find_config(rti->key + 2, + rti->flags & rf_host ? addr->domain : NULL, + rti->basic_errno, rti->more_errno))) { DEBUG(D_retry) debug_printf("No configured retry item for %s%s%s\n", rti->key, - ((rti->flags & rf_host) != 0)? US" or " : US"", - ((rti->flags & rf_host) != 0)? addr->domain : US""); + rti->flags & rf_host ? US" or " : US"", + rti->flags & rf_host ? addr->domain : US""); if (addr == endaddr) timedout_count++; continue; } DEBUG(D_retry) { - if ((rti->flags & rf_host) != 0) - debug_printf("retry for %s (%s) = %s\n", rti->key, - addr->domain, retry->pattern); + if (rti->flags & rf_host) + debug_printf("retry for %s (%s) = %s %d %d\n", rti->key, + addr->domain, retry->pattern, retry->basic_errno, + retry->more_errno); else - debug_printf("retry for %s = %s\n", rti->key, retry->pattern); + debug_printf("retry for %s = %s %d %d\n", rti->key, retry->pattern, + retry->basic_errno, retry->more_errno); } /* Set up the message for the database retry record. Because DBM records have a maximum data length, we enforce a limit. There isn't much point in keeping a huge message here, anyway. */ - message = (rti->basic_errno > 0)? US strerror(rti->basic_errno) : - (rti->message == NULL)? - US"unknown error" : string_printing(rti->message); + message = rti->basic_errno > 0 + ? US strerror(rti->basic_errno) + : rti->message + ? US string_printing(rti->message) + : US"unknown error"; message_length = Ustrlen(message); if (message_length > 150) message_length = 150; @@ -639,11 +665,11 @@ for (i = 0; i < 3; i++) Ignore an old one if it is too old since it was last updated. */ retry_record = dbfn_read(dbm_file, rti->key); - if (retry_record != NULL && - now - retry_record->time_stamp > retry_data_expire) + if ( retry_record + && now - retry_record->time_stamp > retry_data_expire) retry_record = NULL; - if (retry_record == NULL) + if (!retry_record) { retry_record = store_get(sizeof(dbdata_retry) + message_length); message_space = message_length; @@ -658,6 +684,17 @@ for (i = 0; i < 3; i++) /* Compute how long this destination has been failing */ failing_interval = now - retry_record->first_failed; + DEBUG(D_retry) debug_printf("failing_interval=%d message_age=%d\n", + failing_interval, message_age); + + /* For a non-host error, if the message has been on the queue longer + than the recorded time of failure, use the message's age instead. This + can happen when some messages can be delivered and others cannot; a + successful delivery will reset the first_failed time, and this can lead + to a failing message being retried too often. */ + + if (!(rti->flags & rf_host) && message_age > failing_interval) + failing_interval = message_age; /* Search for the current retry rule. The cutoff time of the last rule is handled differently to the others. The rule continues @@ -667,7 +704,7 @@ for (i = 0; i < 3; i++) always times out, but we can't compute a retry time. */ final_rule = NULL; - for (rule = retry->rules; rule != NULL; rule = rule->next) + for (rule = retry->rules; rule; rule = rule->next) { if (failing_interval <= rule->timeout) break; final_rule = rule; @@ -679,10 +716,8 @@ for (i = 0; i < 3; i++) flag is false (can be forced via fixdb from outside, but ensure it is consistent with the rules whenever we go through here). */ - if (rule != NULL) - { + if (rule) retry_record->expired = FALSE; - } /* Otherwise, set the retry timeout expired, and set the final rule as the one from which to compute the next retry time. Subsequent @@ -712,15 +747,23 @@ for (i = 0; i < 3; i++) This implements "timeout this rule if EITHER the host (or routing or directing) has been failing for more than the maximum time, OR if the - message has been on the queue for more than the maximum time." */ - - if (received_time <= retry_record->first_failed && - addr == endaddr && !retry_record->expired && rule != NULL) + message has been on the queue for more than the maximum time." + + February 2006: It is possible that this code is no longer needed + following the change to the retry calculation to use the message age if + it is larger than the time since first failure. It may be that the + expired flag is always set when the other conditions are met. However, + this is a small bit of code, and it does no harm to leave it in place, + just in case. */ + + if ( received_time <= retry_record->first_failed + && addr == endaddr + && !retry_record->expired + && rule) { retry_rule *last_rule; - for (last_rule = rule; - last_rule->next != NULL; - last_rule = last_rule->next); + for (last_rule = rule; last_rule->next; last_rule = last_rule->next) + ; if (now - received_time > last_rule->timeout) { DEBUG(D_retry) debug_printf("on queue longer than maximum retry\n"); @@ -736,9 +779,12 @@ for (i = 0; i < 3; i++) case set the next retry time to now, so that one delivery attempt happens for subsequent messages. */ - if (rule == NULL) next_try = now; else + if (!rule) + next_try = now; + else { - if (rule->rule == 'F') next_try = now + rule->p1; + if (rule->rule == 'F') + next_try = now + rule->p1; else /* rule = 'G' or 'H' */ { int last_predicted_gap = @@ -748,14 +794,13 @@ for (i = 0; i < 3; i++) last_predicted_gap : last_actual_gap; int next_gap = (lastgap * rule->p2)/1000; if (rule->rule == 'G') - { next_try = now + ((lastgap < rule->p1)? rule->p1 : next_gap); - } else /* The 'H' rule */ { next_try = now + rule->p1; if (next_gap > rule->p1) - next_try += random_number(next_gap - rule->p1); + next_try += random_number(next_gap - rule->p1)/2 + + (next_gap - rule->p1)/2; } } } @@ -810,7 +855,6 @@ for (i = 0; i < 3; i++) time was not reached (or because of hosts_max_try). */ if (update_count > 0 && update_count == timedout_count) - { if (!testflag(endaddr, af_retry_skipped)) { DEBUG(D_retry) debug_printf("timed out: all retries expired\n"); @@ -821,7 +865,6 @@ for (i = 0; i < 3; i++) DEBUG(D_retry) debug_printf("timed out but some hosts were skipped\n"); } - } } /* Loop for an address and its parents */ /* If this is a deferred address, and retry processing was requested by @@ -837,7 +880,7 @@ for (i = 0; i < 3; i++) if (i == 2) /* Handling defers */ { - if (endaddr->retries != NULL && timed_out) + if (endaddr->retries && timed_out) { if (last_first == endaddr) paddr = saved_paddr; addr = *paddr; @@ -885,7 +928,7 @@ for (i = 0; i < 3; i++) /* Close and unlock the database */ -if (dbm_file != NULL) dbfn_close(dbm_file); +if (dbm_file) dbfn_close(dbm_file); DEBUG(D_retry) debug_printf("end of retry processing\n"); }