-/*************************************************
-* Do callout verification for an address *
-*************************************************/
-
-/* This function is called from verify_address() when the address has routed to
-a host list, and a callout has been requested. Callouts are expensive; that is
-why a cache is used to improve the efficiency.
-
-Arguments:
- addr the address that's been routed
- host_list the list of hosts to try
- tf the transport feedback block
-
- ifstring "interface" option from transport, or NULL
- portstring "port" option from transport, or NULL
- protocolstring "protocol" option from transport, or NULL
- callout the per-command callout timeout
- callout_overall the overall callout timeout (if < 0 use 4*callout)
- callout_connect the callout connection timeout (if < 0 use callout)
- options the verification options - these bits are used:
- vopt_is_recipient => this is a recipient address
- vopt_callout_no_cache => don't use callout cache
- vopt_callout_fullpm => if postmaster check, do full one
- vopt_callout_random => do the "random" thing
- vopt_callout_recipsender => use real sender for recipient
- vopt_callout_recippmaster => use postmaster for recipient
- se_mailfrom MAIL FROM address for sender verify; NULL => ""
- pm_mailfrom if non-NULL, do the postmaster check with this sender
+/* Check the callout cache.
+Options * pm_mailfrom may be modified by cache partial results.
-Returns: OK/FAIL/DEFER
+Return: TRUE if result found
*/
-static int
-do_callout(address_item *addr, host_item *host_list, transport_feedback *tf,
- int callout, int callout_overall, int callout_connect, int options,
- uschar *se_mailfrom, uschar *pm_mailfrom)
+static BOOL
+cached_callout_lookup(address_item * addr, uschar * address_key,
+ uschar * from_address, int * opt_ptr, uschar ** pm_ptr,
+ int * yield, uschar ** failure_ptr,
+ dbdata_callout_cache * new_domain_record, int * old_domain_res)
{
-int yield = OK;
-int old_domain_cache_result = ccache_accept;
-BOOL done = FALSE;
-uschar *address_key;
-uschar *from_address;
-uschar *random_local_part = NULL;
-const uschar *save_deliver_domain = deliver_domain;
-uschar **failure_ptr = options & vopt_is_recipient
- ? &recipient_verify_failure : &sender_verify_failure;
+int options = *opt_ptr;
open_db dbblock;
open_db *dbm_file = NULL;
-dbdata_callout_cache new_domain_record;
-dbdata_callout_cache_address new_address_record;
-host_item *host;
-time_t callout_start_time;
-
-new_domain_record.result = ccache_unknown;
-new_domain_record.postmaster_result = ccache_unknown;
-new_domain_record.random_result = ccache_unknown;
-
-memset(&new_address_record, 0, sizeof(new_address_record));
-
-/* For a recipient callout, the key used for the address cache record must
-include the sender address if we are using the real sender in the callout,
-because that may influence the result of the callout. */
-
-address_key = addr->address;
-from_address = US"";
-
-if (options & vopt_is_recipient)
- {
- if (options & vopt_callout_recipsender)
- {
- address_key = string_sprintf("%s/<%s>", addr->address, sender_address);
- from_address = sender_address;
- if (cutthrough.delivery) options |= vopt_callout_no_cache;
- }
- else if (options & vopt_callout_recippmaster)
- {
- address_key = string_sprintf("%s/<postmaster@%s>", addr->address,
- qualify_domain_sender);
- from_address = string_sprintf("postmaster@%s", qualify_domain_sender);
- }
- }
-
-/* For a sender callout, we must adjust the key if the mailfrom address is not
-empty. */
-
-else
- {
- from_address = (se_mailfrom == NULL)? US"" : se_mailfrom;
- if (from_address[0] != 0)
- address_key = string_sprintf("%s/<%s>", addr->address, from_address);
- }
/* Open the callout cache database, it it exists, for reading only at this
stage, unless caching has been disabled. */
{
HDEBUG(D_verify) debug_printf("callout cache: disabled by no_cache\n");
}
-else if ((dbm_file = dbfn_open(US"callout", O_RDWR, &dbblock, FALSE)) == NULL)
+else if (!(dbm_file = dbfn_open(US"callout", O_RDWR, &dbblock, FALSE)))
{
HDEBUG(D_verify) debug_printf("callout cache: not available\n");
}
-
-/* If a cache database is available see if we can avoid the need to do an
-actual callout by making use of previously-obtained data. */
-
-if (dbm_file)
+else
{
- dbdata_callout_cache_address *cache_address_record;
- dbdata_callout_cache *cache_record = get_callout_cache_record(dbm_file,
- addr->domain, US"domain",
- callout_cache_domain_positive_expire,
- callout_cache_domain_negative_expire);
+ /* If a cache database is available see if we can avoid the need to do an
+ actual callout by making use of previously-obtained data. */
+
+ dbdata_callout_cache_address * cache_address_record;
+ dbdata_callout_cache * cache_record = get_callout_cache_record(dbm_file,
+ addr->domain, US"domain",
+ callout_cache_domain_positive_expire, callout_cache_domain_negative_expire);
/* If an unexpired cache record was found for this domain, see if the callout
process can be short-circuited. */
not to disturb the cached domain value if this whole verification succeeds
(we don't want it turning into "accept"). */
- old_domain_cache_result = cache_record->result;
+ *old_domain_res = cache_record->result;
- if (cache_record->result == ccache_reject ||
- (*from_address == 0 && cache_record->result == ccache_reject_mfnull))
+ if ( cache_record->result == ccache_reject
+ || *from_address == 0 && cache_record->result == ccache_reject_mfnull)
{
setflag(addr, af_verify_nsfail);
HDEBUG(D_verify)
- debug_printf("callout cache: domain gave initial rejection, or "
- "does not accept HELO or MAIL FROM:<>\n");
+ debug_printf("callout cache: domain gave initial rejection, or "
+ "does not accept HELO or MAIL FROM:<>\n");
setflag(addr, af_verify_nsfail);
addr->user_message = US"(result of an earlier callout reused).";
- yield = FAIL;
+ *yield = FAIL;
*failure_ptr = US"mail";
- goto END_CALLOUT;
+ dbfn_close(dbm_file);
+ return TRUE;
}
/* If a previous check on a "random" local part was accepted, we assume
case ccache_accept:
HDEBUG(D_verify)
debug_printf("callout cache: domain accepts random addresses\n");
- goto END_CALLOUT; /* Default yield is OK */
+ dbfn_close(dbm_file);
+ return TRUE; /* Default yield is OK */
case ccache_reject:
HDEBUG(D_verify)
debug_printf("callout cache: domain rejects random addresses\n");
- options &= ~vopt_callout_random;
- new_domain_record.random_result = ccache_reject;
- new_domain_record.random_stamp = cache_record->random_stamp;
+ *opt_ptr = options & ~vopt_callout_random;
+ new_domain_record->random_result = ccache_reject;
+ new_domain_record->random_stamp = cache_record->random_stamp;
break;
default:
HDEBUG(D_verify)
debug_printf("callout cache: need to check random address handling "
"(not cached or cache expired)\n");
- goto END_CACHE;
+ dbfn_close(dbm_file);
+ return FALSE;
}
/* If a postmaster check is requested, but there was a previous failure,
but has not been done before, we are going to have to do a callout, so skip
remaining cache processing. */
- if (pm_mailfrom)
+ if (*pm_ptr)
{
if (cache_record->postmaster_result == ccache_reject)
- {
- setflag(addr, af_verify_pmfail);
- HDEBUG(D_verify)
- debug_printf("callout cache: domain does not accept "
- "RCPT TO:<postmaster@domain>\n");
- yield = FAIL;
- *failure_ptr = US"postmaster";
- setflag(addr, af_verify_pmfail);
- addr->user_message = US"(result of earlier verification reused).";
- goto END_CALLOUT;
- }
+ {
+ setflag(addr, af_verify_pmfail);
+ HDEBUG(D_verify)
+ debug_printf("callout cache: domain does not accept "
+ "RCPT TO:<postmaster@domain>\n");
+ *yield = FAIL;
+ *failure_ptr = US"postmaster";
+ setflag(addr, af_verify_pmfail);
+ addr->user_message = US"(result of earlier verification reused).";
+ dbfn_close(dbm_file);
+ return TRUE;
+ }
if (cache_record->postmaster_result == ccache_unknown)
- {
- HDEBUG(D_verify)
- debug_printf("callout cache: need to check RCPT "
- "TO:<postmaster@domain> (not cached or cache expired)\n");
- goto END_CACHE;
- }
+ {
+ HDEBUG(D_verify)
+ debug_printf("callout cache: need to check RCPT "
+ "TO:<postmaster@domain> (not cached or cache expired)\n");
+ dbfn_close(dbm_file);
+ return FALSE;
+ }
/* If cache says OK, set pm_mailfrom NULL to prevent a redundant
postmaster check if the address itself has to be checked. Also ensure
*/
HDEBUG(D_verify) debug_printf("callout cache: domain accepts RCPT "
- "TO:<postmaster@domain>\n");
- pm_mailfrom = NULL;
- new_domain_record.postmaster_result = ccache_accept;
- new_domain_record.postmaster_stamp = cache_record->postmaster_stamp;
+ "TO:<postmaster@domain>\n");
+ *pm_ptr = NULL;
+ new_domain_record->postmaster_result = ccache_accept;
+ new_domain_record->postmaster_stamp = cache_record->postmaster_stamp;
}
}
sender address if we are doing a recipient callout with a non-empty sender).
*/
- cache_address_record = (dbdata_callout_cache_address *)
- get_callout_cache_record(dbm_file,
- address_key, US"address",
- callout_cache_positive_expire,
- callout_cache_negative_expire);
+ if (!(cache_address_record = (dbdata_callout_cache_address *)
+ get_callout_cache_record(dbm_file, address_key, US"address",
+ callout_cache_positive_expire, callout_cache_negative_expire)))
+ {
+ dbfn_close(dbm_file);
+ return FALSE;
+ }
- if (cache_address_record)
+ if (cache_address_record->result == ccache_accept)
{
- if (cache_address_record->result == ccache_accept)
- {
- HDEBUG(D_verify)
- debug_printf("callout cache: address record is positive\n");
- }
- else
- {
- HDEBUG(D_verify)
- debug_printf("callout cache: address record is negative\n");
- addr->user_message = US"Previous (cached) callout verification failure";
- *failure_ptr = US"recipient";
- yield = FAIL;
- }
- goto END_CALLOUT;
+ HDEBUG(D_verify)
+ debug_printf("callout cache: address record is positive\n");
+ }
+ else
+ {
+ HDEBUG(D_verify)
+ debug_printf("callout cache: address record is negative\n");
+ addr->user_message = US"Previous (cached) callout verification failure";
+ *failure_ptr = US"recipient";
+ *yield = FAIL;
}
/* Close the cache database while we actually do the callout for real. */
- END_CACHE:
dbfn_close(dbm_file);
- dbm_file = NULL;
+ return TRUE;
}
+return FALSE;
+}
+
+
+/* Write results to callout cache
+*/
+static void
+cache_callout_write(dbdata_callout_cache * dom_rec, const uschar * domain,
+ int done, dbdata_callout_cache_address * addr_rec, uschar * address_key)
+{
+open_db dbblock;
+open_db *dbm_file = NULL;
+
+/* If we get here with done == TRUE, a successful callout happened, and yield
+will be set OK or FAIL according to the response to the RCPT command.
+Otherwise, we looped through the hosts but couldn't complete the business.
+However, there may be domain-specific information to cache in both cases.
+
+The value of the result field in the new_domain record is ccache_unknown if
+there was an error before or with MAIL FROM:, and errno was not zero,
+implying some kind of I/O error. We don't want to write the cache in that case.
+Otherwise the value is ccache_accept, ccache_reject, or ccache_reject_mfnull. */
+
+if (dom_rec->result != ccache_unknown)
+ if (!(dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE)))
+ {
+ HDEBUG(D_verify) debug_printf("callout cache: not available\n");
+ }
+ else
+ {
+ (void)dbfn_write(dbm_file, domain, dom_rec,
+ (int)sizeof(dbdata_callout_cache));
+ HDEBUG(D_verify) debug_printf("wrote callout cache domain record for %s:\n"
+ " result=%d postmaster=%d random=%d\n",
+ domain,
+ dom_rec->result,
+ dom_rec->postmaster_result,
+ dom_rec->random_result);
+ }
+
+/* If a definite result was obtained for the callout, cache it unless caching
+is disabled. */
+
+if (done && addr_rec->result != ccache_unknown)
+ {
+ if (!dbm_file)
+ dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE);
+ if (!dbm_file)
+ {
+ HDEBUG(D_verify) debug_printf("no callout cache available\n");
+ }
+ else
+ {
+ (void)dbfn_write(dbm_file, address_key, addr_rec,
+ (int)sizeof(dbdata_callout_cache_address));
+ HDEBUG(D_verify) debug_printf("wrote %s callout cache address record for %s\n",
+ addr_rec->result == ccache_accept ? "positive" : "negative",
+ address_key);
+ }
+ }
+
+if (dbm_file) dbfn_close(dbm_file);
+}
+
+
+/*************************************************
+* Do callout verification for an address *
+*************************************************/
+
+/* This function is called from verify_address() when the address has routed to
+a host list, and a callout has been requested. Callouts are expensive; that is
+why a cache is used to improve the efficiency.
+
+Arguments:
+ addr the address that's been routed
+ host_list the list of hosts to try
+ tf the transport feedback block
+
+ ifstring "interface" option from transport, or NULL
+ portstring "port" option from transport, or NULL
+ protocolstring "protocol" option from transport, or NULL
+ callout the per-command callout timeout
+ callout_overall the overall callout timeout (if < 0 use 4*callout)
+ callout_connect the callout connection timeout (if < 0 use callout)
+ options the verification options - these bits are used:
+ vopt_is_recipient => this is a recipient address
+ vopt_callout_no_cache => don't use callout cache
+ vopt_callout_fullpm => if postmaster check, do full one
+ vopt_callout_random => do the "random" thing
+ vopt_callout_recipsender => use real sender for recipient
+ vopt_callout_recippmaster => use postmaster for recipient
+ se_mailfrom MAIL FROM address for sender verify; NULL => ""
+ pm_mailfrom if non-NULL, do the postmaster check with this sender
+
+Returns: OK/FAIL/DEFER
+*/
+
+static int
+do_callout(address_item *addr, host_item *host_list, transport_feedback *tf,
+ int callout, int callout_overall, int callout_connect, int options,
+ uschar *se_mailfrom, uschar *pm_mailfrom)
+{
+int yield = OK;
+int old_domain_cache_result = ccache_accept;
+BOOL done = FALSE;
+uschar *address_key;
+uschar *from_address;
+uschar *random_local_part = NULL;
+const uschar *save_deliver_domain = deliver_domain;
+uschar **failure_ptr = options & vopt_is_recipient
+ ? &recipient_verify_failure : &sender_verify_failure;
+dbdata_callout_cache new_domain_record;
+dbdata_callout_cache_address new_address_record;
+host_item *host;
+time_t callout_start_time;
+
+new_domain_record.result = ccache_unknown;
+new_domain_record.postmaster_result = ccache_unknown;
+new_domain_record.random_result = ccache_unknown;
+
+memset(&new_address_record, 0, sizeof(new_address_record));
+
+/* For a recipient callout, the key used for the address cache record must
+include the sender address if we are using the real sender in the callout,
+because that may influence the result of the callout. */
+
+if (options & vopt_is_recipient)
+ if (options & vopt_callout_recipsender)
+ {
+ from_address = sender_address;
+ address_key = string_sprintf("%s/<%s>", addr->address, sender_address);
+ if (cutthrough.delivery) options |= vopt_callout_no_cache;
+ }
+ else if (options & vopt_callout_recippmaster)
+ {
+ from_address = string_sprintf("postmaster@%s", qualify_domain_sender);
+ address_key = string_sprintf("%s/<postmaster@%s>", addr->address,
+ qualify_domain_sender);
+ }
+ else
+ {
+ from_address = US"";
+ address_key = addr->address;
+ }
+
+/* For a sender callout, we must adjust the key if the mailfrom address is not
+empty. */
+
+else
+ {
+ from_address = se_mailfrom ? se_mailfrom : US"";
+ address_key = *from_address
+ ? string_sprintf("%s/<%s>", addr->address, from_address) : addr->address;
+ }
+
+if (cached_callout_lookup(addr, address_key, from_address,
+ &options, &pm_mailfrom, &yield, failure_ptr,
+ &new_domain_record, &old_domain_cache_result))
+ goto END_CALLOUT;
if (!addr->transport)
{
with a random local part, ensure that such a local part is available. If not,
log the fact, but carry on without randomising. */
- if (options & vopt_callout_random && callout_random_local_part != NULL)
+ if (options & vopt_callout_random && callout_random_local_part)
if (!(random_local_part = expand_string(callout_random_local_part)))
log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand "
"callout_random_local_part: %s", expand_string_message);
}
#endif
- new_domain_record.result =
- (old_domain_cache_result == ccache_reject_mfnull)?
- ccache_reject_mfnull: ccache_accept;
+ new_domain_record.result = old_domain_cache_result == ccache_reject_mfnull
+ ? ccache_reject_mfnull : ccache_accept;
/* Do the random local part check first */
/* If we get here with done == TRUE, a successful callout happened, and yield
will be set OK or FAIL according to the response to the RCPT command.
Otherwise, we looped through the hosts but couldn't complete the business.
-However, there may be domain-specific information to cache in both cases.
+However, there may be domain-specific information to cache in both cases. */
-The value of the result field in the new_domain record is ccache_unknown if
-there was an error before or with MAIL FROM:, and errno was not zero,
-implying some kind of I/O error. We don't want to write the cache in that case.
-Otherwise the value is ccache_accept, ccache_reject, or ccache_reject_mfnull. */
-
-if ( !(options & vopt_callout_no_cache)
- && new_domain_record.result != ccache_unknown)
- {
- if ((dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE))
- == NULL)
- {
- HDEBUG(D_verify) debug_printf("callout cache: not available\n");
- }
- else
- {
- (void)dbfn_write(dbm_file, addr->domain, &new_domain_record,
- (int)sizeof(dbdata_callout_cache));
- HDEBUG(D_verify) debug_printf("wrote callout cache domain record for %s:\n"
- " result=%d postmaster=%d random=%d\n",
- addr->domain,
- new_domain_record.result,
- new_domain_record.postmaster_result,
- new_domain_record.random_result);
- }
- }
-
-/* If a definite result was obtained for the callout, cache it unless caching
-is disabled. */
-
-if (done)
- {
- if ( !(options & vopt_callout_no_cache)
- && new_address_record.result != ccache_unknown)
- {
- if (!dbm_file)
- dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE);
- if (!dbm_file)
- {
- HDEBUG(D_verify) debug_printf("no callout cache available\n");
- }
- else
- {
- (void)dbfn_write(dbm_file, address_key, &new_address_record,
- (int)sizeof(dbdata_callout_cache_address));
- HDEBUG(D_verify) debug_printf("wrote %s callout cache address record for %s\n",
- new_address_record.result == ccache_accept ? "positive" : "negative",
- address_key);
- }
- }
- } /* done */
+if (!(options & vopt_callout_no_cache))
+ cache_callout_write(&new_domain_record, addr->domain,
+ done, &new_address_record, address_key);
/* Failure to connect to any host, or any response other than 2xx or 5xx is a
temporary error. If there was only one host, and a response was received, leave
it alone if supplying details. Otherwise, give a generic response. */
-else /* !done */
+if (!done)
{
uschar * dullmsg = string_sprintf("Could not complete %s verify callout",
options & vopt_is_recipient ? "recipient" : "sender");
/* Come here from within the cache-reading code on fast-track exit. */
END_CALLOUT:
-if (dbm_file) dbfn_close(dbm_file);
tls_modify_variables(&tls_in);
return yield;
}