{
if (addr->transport)
tree_add_nonrecipient(
- string_sprintf("%s/%s", addr->unique + 3, addr->transport->name));
+ string_sprintf("%s/%s", addr->unique + 3, addr->transport->drinst.name));
}
/* Non-homonymous child address */
const uschar * save_local = deliver_localpart;
const uschar * save_host = deliver_host;
const uschar * save_address = deliver_host_address;
-uschar * save_rn = router_name, * save_tn = transport_name;
+const uschar * save_rn = router_name;
+const uschar * save_tn = transport_name;
const int save_port = deliver_host_port;
-router_name = addr->router ? addr->router->name : NULL;
+router_name = addr->router ? addr->router->drinst.name : NULL;
deliver_domain = addr->domain;
deliver_localpart = addr->local_part;
deliver_host = addr->host_used ? addr->host_used->name : NULL;
}
else
{
- transport_name = addr->transport->name;
+ const uschar * dr_name = addr->transport->drinst.driver_name;
+ transport_name = addr->transport->drinst.name;
(void) event_raise(addr->transport->event_action, event,
addr->host_used
- || Ustrcmp(addr->transport->driver_name, "smtp") == 0
- || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
- || Ustrcmp(addr->transport->driver_name, "autoreply") == 0
+ || Ustrcmp(dr_name, "smtp") == 0
+ || Ustrcmp(dr_name, "lmtp") == 0
+ || Ustrcmp(dr_name, "autoreply") == 0
? addr->message : NULL,
NULL);
}
if ( testflag(addr, af_pfr)
|| ( success
&& addr->router && addr->router->log_as_local
- && addr->transport && addr->transport->info->local
+ && addr->transport
+ && ((transport_info *)addr->transport->drinst.info)->local
) )
{
if (testflag(addr, af_file) && addr->local_part[0] != '/')
/* For a delivery from a system filter, there may not be a router */
if (addr->router)
- g = string_append(g, 2, US" R=", addr->router->name);
+ g = string_append(g, 2, US" R=", addr->router->drinst.name);
-g = string_append(g, 2, US" T=", addr->transport->name);
+g = string_append(g, 2, US" T=", addr->transport->drinst.name);
if (LOGGING(delivery_size))
g = string_fmt_append(g, " S=%d", transport_count);
/* Local delivery */
-if (addr->transport->info->local)
+if (((transport_info *)addr->transport->drinst.info)->local)
{
if (addr->host_list)
g = string_append(g, 2, US" H=", addr->host_list->name);
if ( LOGGING(smtp_confirmation)
&& addr->message
- && (addr->host_used || Ustrcmp(addr->transport->driver_name, "lmtp") == 0)
+ && ( addr->host_used
+ || Ustrcmp(addr->transport->drinst.driver_name, "lmtp") == 0)
)
{
unsigned lim = big_buffer_size < 1024 ? big_buffer_size : 1024;
if (driver_name)
{
if (driver_kind[1] == 't' && addr->router)
- g = string_append(g, 2, US" R=", addr->router->name);
+ g = string_append(g, 2, US" R=", addr->router->drinst.name);
g = string_fmt_append(g, " %c=%s", toupper(driver_kind[1]), driver_name);
}
else if (driver_kind)
g = string_append(g, 3, US" P=<", used_return_path, US">");
if (addr->router)
- g = string_append(g, 2, US" R=", addr->router->name);
+ g = string_append(g, 2, US" R=", addr->router->drinst.name);
if (addr->transport)
- g = string_append(g, 2, US" T=", addr->transport->name);
+ g = string_append(g, 2, US" T=", addr->transport->drinst.name);
if (addr->host_used)
g = d_hostlog(g, addr);
{
if (addr->transport)
{
- driver_name = addr->transport->name;
+ driver_name = addr->transport->drinst.name;
driver_kind = US" transport";
f.disable_logging = addr->transport->disable_logging;
}
{
if (addr->router)
{
- driver_name = addr->router->name;
+ driver_name = addr->router->drinst.name;
driver_kind = US" router";
f.disable_logging = addr->router->disable_logging;
}
if (fstat(addr->return_file, &statbuf) == 0 && statbuf.st_size > 0)
{
- transport_instance *tb = addr->transport;
+ transport_instance * tb = addr->transport;
/* Handle logging options */
|| result == DEFER && tb->log_defer_output
)
{
- uschar *s;
- FILE *f = Ufopen(addr->return_filename, "rb");
+ uschar * s;
+ FILE * f = Ufopen(addr->return_filename, "rb");
if (!f)
log_write(0, LOG_MAIN|LOG_PANIC, "failed to open %s to log output "
- "from %s transport: %s", addr->return_filename, tb->name,
+ "from %s transport: %s", addr->return_filename, tb->drinst.name,
strerror(errno));
else
if ((s = US Ufgets(big_buffer, big_buffer_size, f)))
*p = 0;
sp = string_printing(big_buffer);
log_write(0, LOG_MAIN, "<%s>: %s transport output: %s",
- addr->address, tb->name, sp);
+ addr->address, tb->drinst.name, sp);
}
(void)fclose(f);
}
else if (tp->expand_gid)
{
GET_OPTION("group");
- if (!route_find_expanded_group(tp->expand_gid, tp->name, US"transport", gidp,
- &addr->message))
+ if (!route_find_expanded_group(tp->expand_gid, tp->drinst.name, US"transport",
+ gidp, &addr->message))
{
common_error(FALSE, addr, ERRNO_GIDFAIL, NULL);
return FALSE;
{
struct passwd *pw;
GET_OPTION("user");
- if (!route_find_expanded_user(tp->expand_uid, tp->name, US"transport", &pw,
- uidp, &(addr->message)))
+ if (!route_find_expanded_user(tp->expand_uid, tp->drinst.name, US"transport",
+ &pw, uidp, &(addr->message)))
{
common_error(FALSE, addr, ERRNO_UIDFAIL, NULL);
return FALSE;
if (!gid_set)
{
common_error(TRUE, addr, ERRNO_GIDFAIL, US"User set without group for "
- "%s transport", tp->name);
+ "%s transport", tp->drinst.name);
return FALSE;
}
if (nuname)
{
common_error(TRUE, addr, ERRNO_UIDFAIL, US"User %ld set for %s transport "
- "is on the %s list", (long int)(*uidp), tp->name, nuname);
+ "is on the %s list", (long int)(*uidp), tp->drinst.name, nuname);
return FALSE;
}
rc = DEFER;
addr->message = size_limit == -1
? string_sprintf("failed to expand message_size_limit "
- "in %s transport: %s", tp->name, expand_string_message)
+ "in %s transport: %s", tp->drinst.name, expand_string_message)
: string_sprintf("invalid message_size_limit "
- "in %s transport: %s", tp->name, expand_string_message);
+ "in %s transport: %s", tp->drinst.name, expand_string_message);
}
else if (size_limit > 0 && message_size > size_limit)
{
previously_transported(address_item *addr, BOOL testing)
{
uschar * s = string_sprintf("%s/%s",
- addr->unique + (testflag(addr, af_homonym) ? 3:0), addr->transport->name);
+ addr->unique + (testflag(addr, af_homonym) ? 3:0),
+ addr->transport->drinst.name);
if (tree_search(tree_nonrecipients, s) != 0)
{
DEBUG(D_deliver|D_route|D_transport)
debug_printf("%s was previously delivered (%s transport): discarded\n",
- addr->address, addr->transport->name);
+ addr->address, addr->transport->drinst.name);
if (!testing) child_done(addr, tod_stamp(tod_log));
return TRUE;
}
pid_t pid;
uschar *working_directory;
address_item *addr2;
-transport_instance *tp = addr->transport;
+transport_instance * tp = addr->transport;
+const uschar * trname = tp->drinst.name;
/* 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. */
{
common_error(TRUE, addr, ERRNO_EXPANDFAIL,
US"Failed to expand return path \"%s\" in %s transport: %s",
- tp->return_path, tp->name, expand_string_message);
+ tp->return_path, trname, expand_string_message);
return;
}
}
if (!(deliver_home = expand_string(rawhome)))
{
common_error(TRUE, addr, ERRNO_EXPANDFAIL, US"home directory \"%s\" failed "
- "to expand for %s transport: %s", rawhome, tp->name,
+ "to expand for %s transport: %s", rawhome, trname,
expand_string_message);
return;
}
if (*deliver_home != '/')
{
common_error(TRUE, addr, ERRNO_NOTABSOLUTE, US"home directory path \"%s\" "
- "is not absolute for %s transport", deliver_home, tp->name);
+ "is not absolute for %s transport", deliver_home, trname);
return;
}
}
if (!(working_directory = expand_string(raw)))
{
common_error(TRUE, addr, ERRNO_EXPANDFAIL, US"current directory \"%s\" "
- "failed to expand for %s transport: %s", raw, tp->name,
+ "failed to expand for %s transport: %s", raw, trname,
expand_string_message);
return;
}
if (*working_directory != '/')
{
common_error(TRUE, addr, ERRNO_NOTABSOLUTE, US"current directory path "
- "\"%s\" is not absolute for %s transport", working_directory, tp->name);
+ "\"%s\" is not absolute for %s transport", working_directory, trname);
return;
}
}
addr->return_filename =
spool_fname(US"msglog", message_subdir, message_id,
- string_sprintf("-%d-%d", getpid(), return_count++));
+ string_sprintf("-%ld-%d", (long)getpid(), return_count++));
if ((addr->return_file = open_msglog_file(addr->return_filename, 0400, &error)) < 0)
{
common_error(TRUE, addr, errno, US"Unable to %s file for %s transport "
- "to return message: %s", error, tp->name, strerror(errno));
+ "to return message: %s", error, trname, strerror(errno));
return;
}
}
FD_CLOEXEC);
exim_setugid(uid, gid, use_initgroups,
string_sprintf("local delivery to %s <%s> transport=%s", addr->local_part,
- addr->address, addr->transport->name));
+ addr->address, addr->transport->drinst.name));
DEBUG(D_deliver)
{
{
BOOL ok = TRUE;
set_process_info("delivering %s to %s using %s", message_id,
- addr->local_part, tp->name);
+ addr->local_part, trname);
/* Setting these globals in the subprocess means we need never clear them */
- transport_name = tp->name;
- if (addr->router) router_name = addr->router->name;
- driver_srcfile = tp->srcfile;
- driver_srcline = tp->srcline;
+ transport_name = trname;
+ if (addr->router) router_name = addr->router->drinst.name;
+ driver_srcfile = tp->drinst.srcfile;
+ driver_srcline = tp->drinst.srcline;
/* If a transport filter has been specified, set up its argument list.
Any errors will get put into the address, and FALSE yielded. */
if (ok)
{
+ transport_info * ti = tp->drinst.info;
debug_print_string(tp->debug_string);
- replicate = !(tp->info->code)(addr->transport, addr);
+ replicate = !(ti->code)(addr->transport, addr);
}
}
if (addr2->transport_return == OK)
{
if (testflag(addr2, af_homonym))
- sprintf(CS big_buffer, "%.500s/%s\n", addr2->unique + 3, tp->name);
+ sprintf(CS big_buffer, "%.500s/%s\n", addr2->unique + 3, trname);
else
sprintf(CS big_buffer, "%.500s\n", addr2->unique);
if (rc < 0 && errno == ECHILD) /* Process has vanished */
{
log_write(0, LOG_MAIN, "%s transport process vanished unexpectedly",
- addr->transport->driver_name);
+ addr->transport->drinst.driver_name);
status = 0;
break;
}
addr->special_action = SPECIAL_FREEZE;
log_write(0, LOG_MAIN|LOG_PANIC, "%s transport process returned non-zero "
"status 0x%04x: %s %d",
- addr->transport->driver_name,
+ addr->transport->drinst.driver_name,
status,
msb == 0 ? "terminated by signal" : "exit code",
code);
if (!(warn_message = expand_string(warn_message)))
log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand \"%s\" (warning "
"message for %s transport): %s", addr->transport->warn_message,
- addr->transport->name, expand_string_message);
+ addr->transport->drinst.name, expand_string_message);
else if ((pid = child_open_exim(&fd, US"tpt-warning-message")) > 0)
{
static BOOL
tpt_parallel_check(transport_instance * tp, address_item * addr, uschar ** key)
{
+const uschar * trname = tp->drinst.name;
unsigned max_parallel;
GET_OPTION("max_parallel");
if (expand_string_message)
{
log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand max_parallel option "
- "in %s transport (%s): %s", tp->name, addr->address,
+ "in %s transport (%s): %s", trname, addr->address,
expand_string_message);
return TRUE;
}
if (max_parallel > 0)
{
- uschar * serialize_key = string_sprintf("tpt-serialize-%s", tp->name);
+ uschar * serialize_key = string_sprintf("tpt-serialize-%s", trname);
if (!enq_start(serialize_key, max_parallel))
{
address_item * next;
DEBUG(D_transport)
debug_printf("skipping tpt %s because concurrency limit %u reached\n",
- tp->name, max_parallel);
+ trname, max_parallel);
do
{
next = addr->next;
address_item *addr2, *addr3, *nextaddr;
int logflags = LOG_MAIN;
int logchar = f.dont_deliver? '*' : '=';
- transport_instance *tp;
+ transport_instance * tp;
uschar * serialize_key = NULL;
+ const uschar * trname;
/* Pick the first undelivered address off the chain */
logflags |= LOG_PANIC;
f.disable_logging = FALSE; /* Jic */
addr->message = addr->router
- ? string_sprintf("No transport set by %s router", addr->router->name)
+ ? string_sprintf("No transport set by %s router", addr->router->drinst.name)
: US"No transport set by system filter";
post_process_one(addr, DEFER, logflags, EXIM_DTYPE_TRANSPORT, 0);
continue;
}
+ trname = tp->drinst.name;
/* Check that this base address hasn't previously been delivered to this
transport. The check is necessary at this point to handle homonymic addresses
if (!batch_id)
{
log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand batch_id option "
- "in %s transport (%s): %s", tp->name, addr->address,
+ "in %s transport (%s): %s", trname, addr->address,
expand_string_message);
batch_count = tp->batch_max;
}
if (!bid)
{
log_write(0, LOG_MAIN|LOG_PANIC, "Failed to expand batch_id option "
- "in %s transport (%s): %s", tp->name, next->address,
+ "in %s transport (%s): %s", trname, next->address,
expand_string_message);
ok = FALSE;
}
of these checks, rather than for all local deliveries, because some local
deliveries (e.g. to pipes) can take a substantial time. */
- if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE)))
+ if (continue_retry_db && continue_retry_db != (open_db *)-1)
+ {
+ DEBUG(D_hints_lookup) debug_printf("using cached retry hintsdb handle\n");
+ dbm_file = continue_retry_db;
+ }
+ else if (!(dbm_file = dbfn_open(US"retry", O_RDONLY, &dbblock, FALSE, TRUE)))
DEBUG(D_deliver|D_retry|D_hints_lookup)
debug_printf("no retry data available\n");
while (addr2)
{
BOOL ok = TRUE; /* to deliver this address */
- uschar *retry_key;
- /* Set up the retry key to include the domain or not, and change its
- leading character from "R" to "T". Must make a copy before doing this,
- because the old key may be pointed to from a "delete" retry item after
- a routing delay. */
-
- retry_key = string_copy(
- tp->retry_use_local_part ? addr2->address_retry_key :
- addr2->domain_retry_key);
- *retry_key = 'T';
+ if (f.queue_2stage)
+ {
+ DEBUG(D_deliver)
+ debug_printf_indent("no router retry check (ph1 qrun)\n");
+ }
+ else
+ {
+ /* Set up the retry key to include the domain or not, and change its
+ leading character from "R" to "T". Must make a copy before doing this,
+ because the old key may be pointed to from a "delete" retry item after
+ a routing delay. */
+ uschar * retry_key = string_copy(tp->retry_use_local_part
+ ? addr2->address_retry_key : addr2->domain_retry_key);
+ *retry_key = 'T';
- /* Inspect the retry data. If there is no hints file, delivery happens. */
+ /* Inspect the retry data. If there is no hints file, delivery happens. */
- if (dbm_file)
- {
- dbdata_retry *retry_record = dbfn_read(dbm_file, retry_key);
+ if (dbm_file)
+ {
+ dbdata_retry * retry_record = dbfn_read(dbm_file, retry_key);
- /* If there is no retry record, delivery happens. If there is,
- remember it exists so it can be deleted after a successful delivery. */
+ /* If there is no retry record, delivery happens. If there is,
+ remember it exists so it can be deleted after a successful delivery. */
- if (retry_record)
- {
- setflag(addr2, af_lt_retry_exists);
+ if (retry_record)
+ {
+ setflag(addr2, af_lt_retry_exists);
- /* A retry record exists for this address. If queue running and not
- forcing, inspect its contents. If the record is too old, or if its
- retry time has come, or if it has passed its cutoff time, delivery
- will go ahead. */
+ /* A retry record exists for this address. If queue running and not
+ forcing, inspect its contents. If the record is too old, or if its
+ retry time has come, or if it has passed its cutoff time, delivery
+ will go ahead. */
- DEBUG(D_retry)
- {
- debug_printf("retry record exists: age=%s ",
- readconf_printtime(now - retry_record->time_stamp));
- debug_printf("(max %s)\n", readconf_printtime(retry_data_expire));
- debug_printf(" time to retry = %s expired = %d\n",
- readconf_printtime(retry_record->next_try - now),
- retry_record->expired);
- }
+ DEBUG(D_retry)
+ {
+ debug_printf("retry record exists: age=%s ",
+ readconf_printtime(now - retry_record->time_stamp));
+ debug_printf("(max %s)\n", readconf_printtime(retry_data_expire));
+ debug_printf(" time to retry = %s expired = %d\n",
+ readconf_printtime(retry_record->next_try - now),
+ retry_record->expired);
+ }
- if (f.queue_running && !f.deliver_force)
- {
- ok = (now - retry_record->time_stamp > retry_data_expire)
- || (now >= retry_record->next_try)
- || retry_record->expired;
+ if (f.queue_running && !f.deliver_force)
+ {
+ ok = (now - retry_record->time_stamp > retry_data_expire)
+ || (now >= retry_record->next_try)
+ || retry_record->expired;
- /* If we haven't reached the retry time, there is one more check
- to do, which is for the ultimate address timeout. */
+ /* If we haven't reached the retry time, there is one more check
+ to do, which is for the ultimate address timeout. */
- if (!ok)
- ok = retry_ultimate_address_timeout(retry_key, addr2->domain,
- retry_record, now);
- }
- }
- else DEBUG(D_retry) debug_printf("no retry record exists\n");
+ if (!ok)
+ ok = retry_ultimate_address_timeout(retry_key, addr2->domain,
+ retry_record, now);
+ }
+ }
+ else DEBUG(D_retry) debug_printf("no retry record exists\n");
+ }
}
/* This address is to be delivered. Leave it on the chain. */
}
}
- if (dbm_file) dbfn_close(dbm_file);
+ if (dbm_file)
+ if (dbm_file != continue_retry_db)
+ { dbfn_close(dbm_file); dbm_file = NULL; }
+ else
+ DEBUG(D_hints_lookup) debug_printf("retaining retry hintsdb handle\n");
/* If there are no addresses left on the chain, they all deferred. Loop
for the next set of addresses. */
if ( tp->shadow
&& ( !tp->shadow_condition
- || expand_check_condition(tp->shadow_condition, tp->name, US"transport")
+ || expand_check_condition(tp->shadow_condition, trname, US"transport")
) )
{
- transport_instance *stp;
- address_item *shadow_addr = NULL;
- address_item **last = &shadow_addr;
+ transport_instance * stp;
+ address_item * shadow_addr = NULL;
+ address_item ** last = &shadow_addr;
- for (stp = transports; stp; stp = stp->next)
- if (Ustrcmp(stp->name, tp->shadow) == 0) break;
+ for (stp = transports; stp; stp = stp->drinst.next)
+ if (Ustrcmp(stp->drinst.name, tp->shadow) == 0) break;
if (!stp)
log_write(0, LOG_MAIN|LOG_PANIC, "shadow transport \"%s\" not found ",
if (shadow_addr)
{
+ const uschar * s_trname = stp->drinst.name;
int save_count = transport_count;
DEBUG(D_deliver|D_transport)
int sresult = shadow_addr->transport_return;
*(uschar **)shadow_addr->shadow_message =
sresult == OK
- ? string_sprintf(" ST=%s", stp->name)
- : string_sprintf(" ST=%s (%s%s%s)", stp->name,
+ ? string_sprintf(" ST=%s", s_trname)
+ : string_sprintf(" ST=%s (%s%s%s)", s_trname,
shadow_addr->basic_errno <= 0
? US""
: US strerror(shadow_addr->basic_errno),
DEBUG(D_deliver|D_transport)
debug_printf("%s shadow transport returned %s for %s\n",
- stp->name, rc_to_string(sresult), shadow_addr->address);
+ s_trname, rc_to_string(sresult), shadow_addr->address);
}
DEBUG(D_deliver|D_transport)
DEBUG(D_deliver|D_transport)
debug_printf("%s transport returned %s for %s\n",
- tp->name, rc_to_string(result), addr2->address);
+ trname, rc_to_string(result), addr2->address);
/* If there is a retry_record, or if delivery is deferred, build a retry
item for setting a new retry time or deleting the old retry record from
*/
-DEBUG(D_deliver) debug_printf("reading pipe for subprocess %d (%s)\n",
- (int)p->pid, eop? "ended" : "not ended yet");
+DEBUG(D_deliver) debug_printf("reading pipe for subprocess %ld (%s)\n",
+ (long)p->pid, eop? "ended" : "not ended yet");
while (!done)
{
size_t required = PIPE_HEADER_SIZE; /* first the pipehaeder, later the data */
ssize_t got;
- DEBUG(D_deliver) debug_printf(
- "expect %lu bytes (pipeheader) from tpt process %d\n", (u_long)required, pid);
+ DEBUG(D_deliver)
+ debug_printf("expect %lu bytes (pipeheader) from tpt process %ld\n",
+ (u_long)required, (long)pid);
/* We require(!) all the PIPE_HEADER_SIZE bytes here, as we know,
they're written in a timely manner, so waiting for the write shouldn't hurt a lot.
if ((got = readn(fd, pipeheader, required)) != required)
{
msg = string_sprintf("got " SSIZE_T_FMT " of %d bytes (pipeheader) "
- "from transport process %d for transport %s",
- got, PIPE_HEADER_SIZE, pid, addr->transport->driver_name);
+ "from transport process %ld for transport %s",
+ got, PIPE_HEADER_SIZE, (long)pid, addr->transport->drinst.driver_name);
done = TRUE;
break;
}
pipeheader[PIPE_HEADER_SIZE] = '\0';
DEBUG(D_deliver)
- debug_printf("got %ld bytes (pipeheader) '%c' from transport process %d\n",
- (long) got, *id, pid);
+ debug_printf("got %ld bytes (pipeheader) '%c' from transport process %ld\n",
+ (long) got, *id, (long)pid);
{
/* If we can't decode the pipeheader, the subprocess seems to have a
if (*endc)
{
msg = string_sprintf("failed to read pipe "
- "from transport process %d for transport %s: error decoding size from header",
- pid, addr->transport->driver_name);
+ "from transport process %ld for transport %s: error decoding size from header",
+ (long)pid, addr ? addr->transport->drinst.driver_name : US"?");
done = TRUE;
break;
}
}
DEBUG(D_deliver)
- debug_printf("expect %lu bytes (pipedata) from transport process %d\n",
- (u_long)required, pid);
+ debug_printf("expect %lu bytes (pipedata) from transport process %ld\n",
+ (u_long)required, (long)pid);
/* Same as above, the transport process will write the bytes announced
in a timely manner, so we can just wait for the bytes, getting less than expected
if ((got = readn(fd, big_buffer, required)) != required)
{
msg = string_sprintf("got only " SSIZE_T_FMT " of " SIZE_T_FMT
- " bytes (pipedata) from transport process %d for transport %s",
- got, required, pid, addr->transport->driver_name);
+ " bytes (pipedata) from transport process %ld for transport %s",
+ got, required, (long)pid, addr->transport->drinst.driver_name);
done = TRUE;
break;
}
{
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);
+ "for transport process %ld for transport %s",
+ (long)pid, addrlist->transport->drinst.driver_name);
done = TRUE;
break;
}
h->dnssec = *ptr == '2' ? DS_YES
: *ptr == '1' ? DS_NO
: DS_UNK;
- ptr++;
addr->host_used = h;
}
- else ptr++;
+ ptr++;
continue_flags = 0;
+#ifndef DISABLE_TLS
if (testflag(addr, af_cert_verified)) continue_flags |= CTF_CV;
+# ifdef SUPPORT_DANE
if (testflag(addr, af_dane_verified)) continue_flags |= CTF_DV;
+# endif
+# ifndef DISABLE_TLS_RESUME
if (testflag(addr, af_tls_resume)) continue_flags |= CTF_TR;
-
+# endif
+#endif
/* Finished with this address */
addr = addr->next;
close(recvd_fd);
DEBUG(D_deliver)
- debug_printf("continue: tpt '%s' host '%s' addr '%s' seq %d\n",
- continue_transport, continue_hostname,
+ debug_printf("continue: fd %d tpt %s host '%s' addr '%s' seq %d\n",
+ recvd_fd, continue_transport, continue_hostname,
continue_host_address, continue_sequence);
break;
}
default:
msg = string_sprintf("malformed data (%d) read from pipe for transport "
- "process %d for transport %s", ptr[-1], pid,
- addr->transport->driver_name);
+ "process %ld for transport %s", ptr[-1], (long)pid,
+ addr ? addr->transport->drinst.driver_name : US"?");
done = TRUE;
break;
}
if (!msg && addr)
msg = string_sprintf("insufficient address data read from pipe "
- "for transport process %d for transport %s", pid,
- addr->transport->driver_name);
+ "for transport process %ld for transport %s", (long)pid,
+ addr->transport->drinst.driver_name);
/* If an error message is set, something has gone wrong in getting back
the delivery data. Put the message into each address and freeze it. */
addr->transport_return = DEFER;
addr->special_action = SPECIAL_FREEZE;
addr->message = msg;
- log_write(0, LOG_MAIN|LOG_PANIC, "Delivery status for %s: %s\n", addr->address, addr->message);
+ log_write(0, LOG_MAIN|LOG_PANIC, "Delivery status for %s: %s\n",
+ addr->address, addr->message);
}
/* Return TRUE to indicate we have got all we need from this process, even
{
if ((pid = parlist[poffset].pid) != 0 && kill(pid, 0) == 0)
{
- DEBUG(D_deliver) debug_printf("process %d still exists: assume "
- "stolen by strace\n", (int)pid);
+ DEBUG(D_deliver) debug_printf("process %ld still exists: assume "
+ "stolen by strace\n", (long)pid);
break; /* With poffset set */
}
}
if (endedpid == pid) goto PROCESS_DONE;
if (endedpid != (pid_t)(-1) || errno != EINTR)
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Unexpected error return "
- "%d (errno = %d) from waitpid() for process %d",
- (int)endedpid, errno, (int)pid);
+ "%d (errno = %d) from waitpid() for process %ld",
+ (int)endedpid, errno, (long)pid);
}
}
}
/* This situation is an error, but it's probably better to carry on looking
for another process than to give up (as we used to do). */
- log_write(0, LOG_MAIN|LOG_PANIC, "Process %d finished: not found in remote "
- "transport process list", pid);
+ log_write(0, LOG_MAIN|LOG_PANIC, "Process %ld finished: not found in remote "
+ "transport process list", (long)pid);
} /* End of the "for" loop */
/* Come here when all the data was completely read after a poll(), and
DEBUG(D_deliver)
{
if (status == 0)
- debug_printf("remote delivery process %d ended\n", (int)pid);
+ debug_printf("remote delivery process %ld ended\n", (long)pid);
else
- debug_printf("remote delivery process %d ended: status=%04x\n", (int)pid,
+ debug_printf("remote delivery process %ld ended: status=%04x\n", (long)pid,
status);
}
msg = string_sprintf("%s transport process returned non-zero status 0x%04x: "
"%s %d",
- addrlist->transport->driver_name,
+ addrlist->transport->drinst.driver_name,
status,
msb == 0 ? "terminated by signal" : "exit code",
code);
{
transport_instance * tp = doneaddr->transport;
if (tp->max_parallel)
- enq_end(string_sprintf("tpt-serialize-%s", tp->name));
+ enq_end(string_sprintf("tpt-serialize-%s", tp->drinst.name));
remote_post_process(doneaddr, LOG_MAIN, NULL, fallback);
}
if (tp->expand_multi_domain)
deliver_set_expansions(addr);
- if (exp_bool(addr, US"transport", tp->name, D_transport,
+ if (exp_bool(addr, US"transport", tp->drinst.name, D_transport,
US"multi_domain", tp->multi_domain, tp->expand_multi_domain,
&multi_domain) != OK)
{
|| ( (
(void)(!tp->expand_multi_domain || ((void)deliver_set_expansions(next), 1)),
exp_bool(addr,
- US"transport", next->transport->name, D_transport,
+ US"transport", next->transport->drinst.name, D_transport,
US"multi_domain", next->transport->multi_domain,
next->transport->expand_multi_domain, &md) == OK
)
f.continue_more = FALSE; /* In case got set for the last lot */
if (continue_transport)
{
- BOOL ok = Ustrcmp(continue_transport, tp->name) == 0;
+ BOOL ok = Ustrcmp(continue_transport, tp->drinst.name) == 0;
/*XXX do we need to check for a DANEd conn vs. a change of domain? */
/* If the transport is about to override the host list do not check
if (ok)
{
- smtp_transport_options_block * ob;
+ transport_info * ti = tp->drinst.info;
+ smtp_transport_options_block * ob = tp->drinst.options_block;
- if ( !( Ustrcmp(tp->info->driver_name, "smtp") == 0
- && (ob = (smtp_transport_options_block *)tp->options_block)
- && ob->hosts_override && ob->hosts
+ if ( !( Ustrcmp(ti->drinfo.driver_name, "smtp") == 0
+ && ob && ob->hosts_override && ob->hosts
)
&& addr->host_list
)
if (!ok)
{
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)
+ Ustrcmp(continue_transport, tp->drinst.name) != 0
+ ? string_sprintf("tpt %s vs %s", continue_transport, tp->drinst.name)
: string_sprintf("no host matching %s", continue_hostname));
if (serialize_key) enq_end(serialize_key);
for (next = addr; ; next = next->next)
{
next->host_list = next->fallback_hosts;
- DEBUG(D_deliver) debug_printf("%s queued for fallback host(s)\n", next->address);
+ DEBUG(D_deliver)
+ debug_printf("%s queued for fallback host(s)\n", next->address);
if (!next->next) break;
}
next->next = addr_fallback;
*/
/*XXX what about firsttime? */
+ /*XXX also, ph1? Note tp->name would possibly change per message,
+ so a check/close/open would be needed. Might was to change that var name
+ "continue_wait_db" as we'd be using it for a non-continued-transport
+ context. */
if (continue_transport && !exim_lockfile_needed())
if (!continue_wait_db)
{
continue_next_id[0] = '\0';
}
- if ((pid = exim_fork(US"transport")) == 0)
+ if ((pid = exim_fork(f.queue_2stage ? US"transport ph1":US"transport")) == 0)
{
int fd = pfd[pipe_write];
host_item *h;
/* Setting these globals in the subprocess means we need never clear them */
- transport_name = tp->name;
- if (addr->router) router_name = addr->router->name;
- driver_srcfile = tp->srcfile;
- driver_srcline = tp->srcline;
+ transport_name = tp->drinst.name;
+ if (addr->router) router_name = addr->router->drinst.name;
+ driver_srcfile = tp->drinst.srcfile;
+ driver_srcline = tp->drinst.srcline;
/* There are weird circumstances in which logging is disabled */
f.disable_logging = tp->disable_logging;
exim_setugid(uid, gid, use_initgroups,
string_sprintf("remote delivery to %s with transport=%s",
- addr->address, tp->name));
+ addr->address, tp->drinst.name));
/* Close the unwanted half of this process' pipe, set the process state,
and run the transport. Afterwards, transport_count will contain the number
of bytes written. */
(void)close(pfd[pipe_read]);
- set_process_info("delivering %s using %s", message_id, tp->name);
+ set_process_info("delivering %s using %s", message_id, tp->drinst.name);
debug_print_string(tp->debug_string);
- if (!(tp->info->code)(addr->transport, addr)) replicate_status(addr);
+
+ {
+ transport_info * ti = tp->drinst.info;
+ if (!(ti->code)(addr->transport, addr)) /* Call the transport */
+ replicate_status(addr);
+ }
set_process_info("delivering %s (just run %s for %s%s in subprocess)",
- message_id, tp->name, addr->address, addr->next ? ", ..." : "");
+ message_id, tp->drinst.name, addr->address, addr->next ? ", ..." : "");
/* Ensure any cached resources that we used are now released */
{
uschar * ptr;
+#ifndef DISABLE_TLS
/* The certificate verification status goes into the flags, in A0 */
if (tls_out.certificate_verified) setflag(addr, af_cert_verified);
-#ifdef SUPPORT_DANE
+# ifdef SUPPORT_DANE
if (tls_out.dane_verified) setflag(addr, af_dane_verified);
-#endif
-#ifndef DISABLE_TLS_RESUME
+# endif
+# ifndef DISABLE_TLS_RESUME
if (tls_out.resumption & RESUME_USED) setflag(addr, af_tls_resume);
-#endif
+# endif
/* Use an X item only if there's something to send */
-#ifndef DISABLE_TLS
if (addr->cipher)
{
ptr = big_buffer + sprintf(CS big_buffer, "%.128s", addr->cipher) + 1;
wait-db ones, but for all continue-more ones (though any after the
delivery proc has the info are pointless). */
- if (continue_hostname)
+ if (continue_hostname && continue_fd >= 0)
{
{
uschar * ptr = big_buffer;
if (continue_transport)
{
par_reduce(0, fallback);
- if (!continue_next_id && continue_wait_db)
+ if (!*continue_next_id && continue_wait_db)
{ dbfn_close_multi(continue_wait_db); continue_wait_db = NULL; }
}
continue_closedown(void)
{
if (continue_transport)
- for (transport_instance * t = transports; t; t = t->next)
- if (Ustrcmp(t->name, continue_transport) == 0)
+ for (transport_instance * t = transports; t; t = t->drinst.next)
+ if (Ustrcmp(t->drinst.name, continue_transport) == 0)
{
- if (t->info->closedown) (t->info->closedown)(t);
+ transport_info * ti = t->drinst.info;
+ if (ti->closedown) (ti->closedown)(t);
continue_transport = NULL;
break;
}
/* Make a subprocess to send a message, using its stdin */
if ((pid = child_open_exim(&fd, US"bounce-message")) < 0)
- 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));
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Process %ld (parent %ld) failed to "
+ "create child process to send failure message: %s",
+ (long)getpid(), (long)getppid(), strerror(errno));
/* Creation of child succeeded */
{
/* must be decoded from xtext: see RFC 3461:6.3a */
uschar * xdec_envid;
- if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0)
+ if (xtextdecode(dsn_envid, &xdec_envid) > 0)
fprintf(fp, "Original-Envelope-ID: %s\n", dsn_envid);
else
fprintf(fp, "X-Original-Envelope-ID: error decoding xtext formatted ENVID\n");
{
/* must be decoded from xtext: see RFC 3461:6.3a */
uschar *xdec_envid;
- if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0)
+ if (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 formatted ENVID\n");
"DSN: envid: %s ret: %d\n"
"DSN: Final recipient: %s\n"
"DSN: Remote SMTP server supports DSN: %d\n",
- a->router ? a->router->name : US"(unknown)",
+ a->router ? a->router->drinst.name : US"(unknown)",
a->address,
sender_address,
a->dsn_orcpt ? a->dsn_orcpt : US"NULL",
int fd;
pid_t pid = child_open_exim(&fd, US"DSN");
- DEBUG(D_deliver) debug_printf("DSN: child_open_exim returns: %d\n", pid);
+ DEBUG(D_deliver) debug_printf("DSN: child_open_exim returns: %ld\n", (long)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 success-dsn message: %s", getpid(),
- getppid(), strerror(errno));
+ log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Process %ld (parent %ld) failed to "
+ "create child process to send success-dsn message: %s",
+ (long)getpid(), (long)getppid(), strerror(errno));
DEBUG(D_deliver) debug_printf("DSN: child_open_exim failed\n");
}
if (dsn_envid)
{ /* must be decoded from xtext: see RFC 3461:6.3a */
uschar * xdec_envid;
- if (auth_xtextdecode(dsn_envid, &xdec_envid) > 0)
+ if (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 formatted ENVID\n");
time_t now;
address_item * addr_last;
uschar * filter_message, * info;
-open_db dbblock, * dbm_file;
+open_db dbblock, * dbm_file = NULL;
extern int acl_where;
CONTINUED_ID:
info = queue_run_pid == (pid_t)0
? string_sprintf("delivering %s", id)
- : string_sprintf("delivering %s (queue run pid %d)", id, queue_run_pid);
+ : string_sprintf("delivering %s (queue run pid %ld)", id, (long)queue_run_pid);
/* If the D_process_info bit is on, set_process_info() will output debugging
information. If not, we want to show this initial information if D_deliver or
RDO_REALLOG |
RDO_REWRITE,
NULL, /* No :include: restriction (not used in filter) */
- NULL, /* No sieve vacation directory (not sieve!) */
- NULL, /* No sieve enotify mailto owner (not sieve!) */
- NULL, /* No sieve user address (not sieve!) */
- NULL, /* No sieve subaddress (not sieve!) */
+ NULL, /* No sieve info (not sieve!) */
&ugid, /* uid/gid data */
&addr_new, /* Where to hang generated addresses */
&filter_message, /* Where to put error message */
if (tpname)
{
transport_instance *tp;
- for (tp = transports; tp; tp = tp->next)
- if (Ustrcmp(tp->name, tpname) == 0)
+ for (tp = transports; tp; tp = tp->drinst.next)
+ if (Ustrcmp(tp->drinst.name, tpname) == 0)
{ p->transport = tp; break; }
if (!tp)
p->message = string_sprintf("failed to find \"%s\" transport "
queue run don't bother checking domain- or address-retry info; they will take
effect on the second stage. */
- if (f.queue_2stage)
- dbm_file = NULL;
- else
+ if (!f.queue_2stage)
{
/* If we have transaction-capable hintsdbs, open the retry db without
locking, and leave open for the transport process and for subsequent
- deliveries. If the open fails, tag that explicitly for the transport but
- retry the open next time around, in case it was created in the interim. */
+ deliveries. Use a writeable open as we can keep it open all the way through
+ to writing retry records if needed due to message fails.
+ If the open fails, tag that explicitly for the transport but retry the open
+ next time around, in case it was created in the interim.
+ If non-transaction, we are only reading records at this stage and
+ we close the db before running the transport.
+ Either way we do a non-creating open. */
if (continue_retry_db == (open_db *)-1)
continue_retry_db = NULL;
if (continue_retry_db)
+ {
+ DEBUG(D_hints_lookup) debug_printf("using cached retry hintsdb handle\n");
dbm_file = continue_retry_db;
- else if (!exim_lockfile_needed() && continue_transport)
+ }
+ else if (!exim_lockfile_needed())
{
- dbm_file = dbfn_open_multi(US"retry", O_RDONLY, &dbblock);
+ dbm_file = dbfn_open_multi(US"retry", O_RDWR, &dbblock);
continue_retry_db = dbm_file ? dbm_file : (open_db *)-1;
}
else
{
int rc;
tree_node * tnode;
- dbdata_retry * domain_retry_record, * address_retry_record;
+ dbdata_retry * domain_retry_record = NULL, * address_retry_record = NULL;
addr = addr_new;
addr_new = addr->next;
transport_instance * save_t = addr->transport;
transport_instance * t = store_get(sizeof(*t), save_t);
*t = *save_t;
- t->name = US"**bypassed**";
+ t->drinst.name = US"**bypassed**";
addr->transport = t;
(void)post_process_one(addr, OK, LOG_MAIN, EXIM_DTYPE_TRANSPORT, '=');
addr->transport= save_t;
delivery. */
DEBUG(D_deliver|D_route)
- debug_printf("queued for %s transport\n", addr->transport->name);
+ debug_printf("queued for %s transport\n", addr->transport->drinst.name);
addr->next = addr_local;
addr_local = addr;
continue; /* with the next new address */
continue;
}
- /* Get the routing retry status, saving the two retry keys (with and
- without the local part) for subsequent use. If there is no retry record for
- the standard address routing retry key, we look for the same key with the
- sender attached, because this form is used by the smtp transport after a
- 4xx response to RCPT when address_retry_include_sender is true. */
-
- addr->domain_retry_key = string_sprintf("R:%s", addr->domain);
- addr->address_retry_key = string_sprintf("R:%s@%s", addr->local_part,
- addr->domain);
-
- if (dbm_file)
+ if (f.queue_2stage)
{
- domain_retry_record = dbfn_read(dbm_file, addr->domain_retry_key);
- if ( domain_retry_record
- && now - domain_retry_record->time_stamp > retry_data_expire
- )
+ DEBUG(D_deliver)
+ debug_printf_indent("no router retry check (ph1 qrun)\n");
+ }
+ else
+ {
+ /* Get the routing retry status, saving the two retry keys (with and
+ without the local part) for subsequent use. If there is no retry record
+ for the standard address routing retry key, we look for the same key with
+ the sender attached, because this form is used by the smtp transport after
+ a 4xx response to RCPT when address_retry_include_sender is true. */
+
+ DEBUG(D_deliver|D_retry)
{
- DEBUG(D_deliver|D_retry)
- debug_printf("domain retry record present but expired\n");
- domain_retry_record = NULL; /* Ignore if too old */
+ debug_printf_indent("checking router retry status\n");
+ acl_level++;
}
+ addr->domain_retry_key = string_sprintf("R:%s", addr->domain);
+ addr->address_retry_key = string_sprintf("R:%s@%s", addr->local_part,
+ addr->domain);
- address_retry_record = dbfn_read(dbm_file, addr->address_retry_key);
- if ( address_retry_record
- && now - address_retry_record->time_stamp > retry_data_expire
- )
+ if (dbm_file)
{
- DEBUG(D_deliver|D_retry)
- debug_printf("address retry record present but expired\n");
- address_retry_record = NULL; /* Ignore if too old */
- }
+ domain_retry_record = dbfn_read(dbm_file, addr->domain_retry_key);
+ if ( domain_retry_record
+ && now - domain_retry_record->time_stamp > retry_data_expire
+ )
+ {
+ DEBUG(D_deliver|D_retry)
+ debug_printf_indent("domain retry record present but expired\n");
+ domain_retry_record = NULL; /* Ignore if too old */
+ }
- if (!address_retry_record)
- {
- uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
- sender_address);
- address_retry_record = dbfn_read(dbm_file, altkey);
- if ( address_retry_record
- && now - address_retry_record->time_stamp > retry_data_expire)
+ address_retry_record = dbfn_read(dbm_file, addr->address_retry_key);
+ if ( address_retry_record
+ && now - address_retry_record->time_stamp > retry_data_expire
+ )
{
DEBUG(D_deliver|D_retry)
- debug_printf("address<sender> retry record present but expired\n");
- address_retry_record = NULL; /* Ignore if too old */
+ debug_printf_indent("address retry record present but expired\n");
+ address_retry_record = NULL; /* Ignore if too old */
}
- }
- }
- else
- domain_retry_record = address_retry_record = NULL;
- DEBUG(D_deliver|D_retry)
- {
- if (!domain_retry_record)
- debug_printf("no domain retry record\n");
- else
- debug_printf("have domain retry record; next_try = now%+d\n",
- f.running_in_test_harness ? 0 :
- (int)(domain_retry_record->next_try - now));
+ if (!address_retry_record)
+ {
+ uschar *altkey = string_sprintf("%s:<%s>", addr->address_retry_key,
+ sender_address);
+ address_retry_record = dbfn_read(dbm_file, altkey);
+ if ( address_retry_record
+ && now - address_retry_record->time_stamp > retry_data_expire)
+ {
+ DEBUG(D_deliver|D_retry)
+ debug_printf_indent("address<sender> retry record present but expired\n");
+ address_retry_record = NULL; /* Ignore if too old */
+ }
+ }
+ }
- if (!address_retry_record)
- debug_printf("no address retry record\n");
- else
- debug_printf("have address retry record; next_try = now%+d\n",
- f.running_in_test_harness ? 0 :
- (int)(address_retry_record->next_try - now));
+ DEBUG(D_deliver|D_retry)
+ {
+ if (!domain_retry_record)
+ debug_printf_indent("no domain retry record\n");
+ else
+ debug_printf_indent("have domain retry record; next_try = now%+d\n",
+ f.running_in_test_harness ? 0 :
+ (int)(domain_retry_record->next_try - now));
+
+ if (!address_retry_record)
+ debug_printf_indent("no address retry record\n");
+ else
+ debug_printf_indent("have address retry record; next_try = now%+d\n",
+ f.running_in_test_harness ? 0 :
+ (int)(address_retry_record->next_try - now));
+ acl_level--;
+ }
}
/* If we are sending a message down an existing SMTP connection, we must
}
}
- /* The database is closed while routing is actually happening. Requests to
- update it are put on a chain and all processed together at the end. */
+ /* If not transaction-capable, the database is closed while routing is
+ actually happening. Requests to update it are put on a chain and all processed
+ together at the end. */
- if (dbm_file && !continue_retry_db)
- { dbfn_close(dbm_file); dbm_file = NULL; }
+ if (dbm_file)
+ if (exim_lockfile_needed())
+ { dbfn_close(dbm_file); continue_retry_db = dbm_file = NULL; }
+ else
+ DEBUG(D_hints_lookup) debug_printf("retaining retry hintsdb handle\n");
/* If queue_domains is set, we don't even want to try routing addresses in
those domains. During queue runs, queue_domains is forced to be unset.
} /* Loop to process any child addresses that the routers created, and
any rerouted addresses that got put back on the new chain. */
-if (dbm_file) /* Can only be continue_retry_db */
- { dbfn_close_multi(continue_retry_db); continue_retry_db = dbm_file = NULL; }
-
/* Debugging: show the results of the routing */
DEBUG(D_deliver|D_retry|D_route)
/* If this is a run to continue deliveries to an external channel that is
-already set up, defer any local deliveries.
-
-jgh 2020/12/20: I don't see why; locals should be quick.
-The defer goes back to version 1.62 in 1997. A local being still deliverable
-during a continued run might result from something like a defer during the
-original delivery, eg. in a DB lookup. Unlikely but possible.
+already set up, defer any local deliveries because we are handling remotes.
To avoid delaying a local when combined with a callout-hold for a remote
delivery, test continue_sequence rather than continue_transport. */
/* In a normal configuration, we now update the retry database. This is done in
one fell swoop at the end in order not to keep opening and closing (and
-locking) the database. The code for handling retries is hived off into a
-separate module for convenience. We pass it the addresses of the various
-chains, because deferred addresses can get moved onto the failed chain if the
+locking) the database (at least, for non-transaction-capable DBs.
+The code for handling retries is hived off into a separate module for
+convenience. We pass it the addresses of the various chains,
+because deferred addresses can get moved onto the failed chain if the
retry cutoff time has expired for all alternative destinations. Bypass the
updating of the database if the -N flag is set, which is a debugging thing that
prevents actual delivery. */
DELIVERY_TIDYUP:
+if (dbm_file) /* Can only be continue_retry_db */
+ {
+ DEBUG(D_hints_lookup) debug_printf("final close of cached retry db\n");
+ dbfn_close_multi(continue_retry_db);
+ continue_retry_db = dbm_file = NULL;
+ }
+
/* If there are now no deferred addresses, we are done. Preserve the
message log if so configured, and we are using them. Otherwise, sling it.
Then delete the message itself. */