*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions concerned with verifying things. The original code for callout
if (!(cache_record = dbfn_read_with_length(dbm_file, key, &length)))
{
- HDEBUG(D_verify) debug_printf("callout cache: no %s record found for %s\n", type, key);
+ HDEBUG(D_verify) debug_printf_indent("callout cache: no %s record found for %s\n", type, key);
return NULL;
}
if (now - cache_record->time_stamp > expire)
{
- HDEBUG(D_verify) debug_printf("callout cache: %s record expired for %s\n", type, key);
+ HDEBUG(D_verify) debug_printf_indent("callout cache: %s record expired for %s\n", type, key);
return NULL;
}
cache_record->random_result = ccache_unknown;
}
-HDEBUG(D_verify) debug_printf("callout cache: found %s record for %s\n", type, key);
+HDEBUG(D_verify) debug_printf_indent("callout cache: found %s record for %s\n", type, key);
return cache_record;
}
if (options & vopt_callout_no_cache)
{
- HDEBUG(D_verify) debug_printf("callout cache: disabled by no_cache\n");
+ HDEBUG(D_verify) debug_printf_indent("callout cache: disabled by no_cache\n");
}
else if (!(dbm_file = dbfn_open(US"callout", O_RDWR, &dbblock, FALSE, TRUE)))
{
- HDEBUG(D_verify) debug_printf("callout cache: not available\n");
+ HDEBUG(D_verify) debug_printf_indent("callout cache: not available\n");
}
else
{
|| *from_address == 0 && cache_record->result == ccache_reject_mfnull)
{
HDEBUG(D_verify)
- debug_printf("callout cache: domain gave initial rejection, or "
+ debug_printf_indent("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).";
{
case ccache_accept:
HDEBUG(D_verify)
- debug_printf("callout cache: domain accepts random addresses\n");
+ debug_printf_indent("callout cache: domain accepts random addresses\n");
*failure_ptr = US"random";
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");
+ debug_printf_indent("callout cache: domain rejects random addresses\n");
*opt_ptr = options & ~vopt_callout_random;
new_domain_record->random_result = ccache_reject;
new_domain_record->random_stamp = cache_record->random_stamp;
default:
HDEBUG(D_verify)
- debug_printf("callout cache: need to check random address handling "
+ debug_printf_indent("callout cache: need to check random address handling "
"(not cached or cache expired)\n");
dbfn_close(dbm_file);
return FALSE;
{
setflag(addr, af_verify_pmfail);
HDEBUG(D_verify)
- debug_printf("callout cache: domain does not accept "
+ debug_printf_indent("callout cache: domain does not accept "
"RCPT TO:<postmaster@domain>\n");
*yield = FAIL;
*failure_ptr = US"postmaster";
if (cache_record->postmaster_result == ccache_unknown)
{
HDEBUG(D_verify)
- debug_printf("callout cache: need to check RCPT "
+ debug_printf_indent("callout cache: need to check RCPT "
"TO:<postmaster@domain> (not cached or cache expired)\n");
dbfn_close(dbm_file);
return FALSE;
that the value in the cache record is preserved (with its old timestamp).
*/
- HDEBUG(D_verify) debug_printf("callout cache: domain accepts RCPT "
+ HDEBUG(D_verify) debug_printf_indent("callout cache: domain accepts RCPT "
"TO:<postmaster@domain>\n");
*pm_ptr = NULL;
new_domain_record->postmaster_result = ccache_accept;
if (cache_address_record->result == ccache_accept)
{
HDEBUG(D_verify)
- debug_printf("callout cache: address record is positive\n");
+ debug_printf_indent("callout cache: address record is positive\n");
}
else
{
HDEBUG(D_verify)
- debug_printf("callout cache: address record is negative\n");
+ debug_printf_indent("callout cache: address record is negative\n");
addr->user_message = US"Previous (cached) callout verification failure";
*failure_ptr = US"recipient";
*yield = FAIL;
if (dom_rec->result != ccache_unknown)
if (!(dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE, TRUE)))
{
- HDEBUG(D_verify) debug_printf("callout cache: not available\n");
+ HDEBUG(D_verify) debug_printf_indent("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"
+ HDEBUG(D_verify) debug_printf_indent("wrote callout cache domain record for %s:\n"
" result=%d postmaster=%d random=%d\n",
domain,
dom_rec->result,
dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE, TRUE);
if (!dbm_file)
{
- HDEBUG(D_verify) debug_printf("no callout cache available\n");
+ HDEBUG(D_verify) debug_printf_indent("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",
+ HDEBUG(D_verify) debug_printf_indent("wrote %s callout cache address record for %s\n",
addr_rec->result == ccache_accept ? "positive" : "negative",
address_key);
}
{
HDEBUG(D_verify) debug_printf("cannot callout via null transport\n");
}
+
else if (Ustrcmp(addr->transport->driver_name, "smtp") != 0)
log_write(0, LOG_MAIN|LOG_PANIC|LOG_CONFIG_FOR, "callout transport '%s': %s is non-smtp",
addr->transport->name, addr->transport->driver_name);
{
smtp_transport_options_block *ob =
(smtp_transport_options_block *)addr->transport->options_block;
+ smtp_context * sx = NULL;
/* The information wasn't available in the cache, so we have to do a real
callout and save the result in the cache for next time, unless no_cache is set,
{
int host_af;
int port = 25;
- uschar *interface = NULL; /* Outgoing interface to use; NULL => any */
- smtp_context sx;
+ uschar * interface = NULL; /* Outgoing interface to use; NULL => any */
if (!host->address)
{
log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address,
addr->message);
- sx.addrlist = addr;
- sx.conn_args.host = host;
- sx.conn_args.host_af = host_af,
- sx.port = port;
- sx.conn_args.interface = interface;
- sx.helo_data = tf->helo_data;
- sx.conn_args.tblock = addr->transport;
- sx.verify = TRUE;
+ if (!sx) sx = store_get(sizeof(*sx), TRUE); /* tainted buffers */
+ memset(sx, 0, sizeof(*sx));
+
+ sx->addrlist = addr;
+ sx->conn_args.host = host;
+ sx->conn_args.host_af = host_af,
+ sx->port = port;
+ sx->conn_args.interface = interface;
+ sx->helo_data = tf->helo_data;
+ sx->conn_args.tblock = addr->transport;
+ sx->verify = TRUE;
tls_retry_connection:
/* Set the address state so that errors are recorded in it */
SMTP command to send. If we tried TLS but it failed, try again without
if permitted */
- yield = smtp_setup_conn(&sx, FALSE);
+ yield = smtp_setup_conn(sx, FALSE);
#ifndef DISABLE_TLS
if ( yield == DEFER
&& addr->basic_errno == ERRNO_TLSFAILURE
"%s: callout unencrypted to %s [%s] (not in hosts_require_tls)",
addr->message, host->name, host->address);
addr->transport_return = PENDING_DEFER;
- yield = smtp_setup_conn(&sx, TRUE);
+ yield = smtp_setup_conn(sx, TRUE);
}
#endif
if (yield != OK)
addr->authenticator = client_authenticator;
addr->auth_id = client_authenticated_id;
- sx.from_addr = from_address;
- sx.first_addr = sx.sync_addr = addr;
- sx.ok = FALSE; /*XXX these 3 last might not be needed for verify? */
- sx.send_rset = TRUE;
- sx.completed_addr = FALSE;
+ sx->from_addr = from_address;
+ sx->first_addr = sx->sync_addr = addr;
+ sx->ok = FALSE; /*XXX these 3 last might not be needed for verify? */
+ sx->send_rset = TRUE;
+ sx->completed_addr = FALSE;
new_domain_record.result = old_domain_cache_result == ccache_reject_mfnull
? ccache_reject_mfnull : ccache_accept;
Avoid using a SIZE option on the MAIL for all random-rcpt checks.
*/
- sx.avoid_option = OPTION_SIZE;
+ sx->avoid_option = OPTION_SIZE;
/* Remember when we last did a random test */
new_domain_record.random_stamp = time(NULL);
- if (smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0)
+ if (smtp_write_mail_and_rcpt_cmds(sx, &yield) == 0)
switch(addr->transport_return)
{
case PENDING_OK: /* random was accepted, unfortunately */
goto no_conn;
case FAIL: /* rejected: the preferred result */
new_domain_record.random_result = ccache_reject;
- sx.avoid_option = 0;
+ sx->avoid_option = 0;
/* Between each check, issue RSET, because some servers accept only
one recipient after MAIL FROM:<>.
XXX We don't care about that for postmaster_full. Should we? */
if ((done =
- smtp_write_command(&sx, SCMD_FLUSH, "RSET\r\n") >= 0 &&
- smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2', callout)))
+ smtp_write_command(sx, SCMD_FLUSH, "RSET\r\n") >= 0 &&
+ smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', callout)))
break;
HDEBUG(D_acl|D_v)
debug_printf_indent("problem after random/rset/mfrom; reopen conn\n");
random_local_part = NULL;
#ifndef DISABLE_TLS
- tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+ tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
#endif
HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
- (void)close(sx.cctx.sock);
- sx.cctx.sock = -1;
+ (void)close(sx->cctx.sock);
+ sx->cctx.sock = -1;
#ifndef DISABLE_EVENT
(void) event_raise(addr->transport->event_action,
US"tcp:close", NULL);
#endif
addr->address = main_address;
addr->transport_return = PENDING_DEFER;
- sx.first_addr = sx.sync_addr = addr;
- sx.ok = FALSE;
- sx.send_rset = TRUE;
- sx.completed_addr = FALSE;
+ sx->first_addr = sx->sync_addr = addr;
+ sx->ok = FALSE;
+ sx->send_rset = TRUE;
+ sx->completed_addr = FALSE;
goto tls_retry_connection;
case DEFER: /* 4xx response to random */
break; /* Just to be clear. ccache_unknown, !done. */
/* Re-setup for main verify, or for the error message when failing */
addr->address = main_address;
addr->transport_return = PENDING_DEFER;
- sx.first_addr = sx.sync_addr = addr;
- sx.ok = FALSE;
- sx.send_rset = TRUE;
- sx.completed_addr = FALSE;
+ sx->first_addr = sx->sync_addr = addr;
+ sx->ok = FALSE;
+ sx->send_rset = TRUE;
+ sx->completed_addr = FALSE;
}
else
done = TRUE;
if (done)
{
if (!(options & vopt_is_recipient && options & vopt_callout_no_cache))
- sx.avoid_option = OPTION_SIZE;
+ sx->avoid_option = OPTION_SIZE;
done = FALSE;
- switch(smtp_write_mail_and_rcpt_cmds(&sx, &yield))
+ switch(smtp_write_mail_and_rcpt_cmds(sx, &yield))
{
case 0: switch(addr->transport_return) /* ok so far */
{
case PENDING_OK: done = TRUE;
new_address_record.result = ccache_accept;
break;
- case FAIL: done = TRUE;
+ case FAIL: done = TRUE;
yield = FAIL;
*failure_ptr = US"recipient";
new_address_record.result = ccache_reject;
break;
- default: break;
+ default: break;
}
break;
case -1: /* MAIL response error */
*failure_ptr = US"mail";
- if (errno == 0 && sx.buffer[0] == '5')
+ if (errno == 0 && sx->buffer[0] == '5')
{
setflag(addr, af_verify_nsfail);
if (from_address[0] == 0)
cancel_cutthrough_connection(TRUE, US"postmaster verify");
HDEBUG(D_acl|D_v) debug_printf_indent("Cutthrough cancelled by presence of postmaster verify\n");
- done = smtp_write_command(&sx, SCMD_FLUSH, "RSET\r\n") >= 0
- && smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2', callout);
+ done = smtp_write_command(sx, SCMD_FLUSH, "RSET\r\n") >= 0
+ && smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', callout);
if (done)
{
addr->address = string_sprintf("postmaster@%.1000s", addr->domain);
addr->transport_return = PENDING_DEFER;
- sx.from_addr = pm_mailfrom;
- sx.first_addr = sx.sync_addr = addr;
- sx.ok = FALSE;
- sx.send_rset = TRUE;
- sx.completed_addr = FALSE;
- sx.avoid_option = OPTION_SIZE;
+ sx->from_addr = pm_mailfrom;
+ sx->first_addr = sx->sync_addr = addr;
+ sx->ok = FALSE;
+ sx->send_rset = TRUE;
+ sx->completed_addr = FALSE;
+ sx->avoid_option = OPTION_SIZE;
- if( smtp_write_mail_and_rcpt_cmds(&sx, &yield) == 0
+ if( smtp_write_mail_and_rcpt_cmds(sx, &yield) == 0
&& addr->transport_return == PENDING_OK
)
done = TRUE;
else
done = (options & vopt_callout_fullpm) != 0
- && smtp_write_command(&sx, SCMD_FLUSH,
+ && smtp_write_command(sx, SCMD_FLUSH,
"RCPT TO:<postmaster>\r\n") >= 0
- && smtp_read_response(&sx, sx.buffer,
- sizeof(sx.buffer), '2', callout);
+ && smtp_read_response(sx, sx->buffer,
+ sizeof(sx->buffer), '2', callout);
/* Sort out the cache record */
if (done)
new_domain_record.postmaster_result = ccache_accept;
- else if (errno == 0 && sx.buffer[0] == '5')
+ else if (errno == 0 && sx->buffer[0] == '5')
{
*failure_ptr = US"postmaster";
setflag(addr, af_verify_pmfail);
{
case ETIMEDOUT:
HDEBUG(D_verify) debug_printf("SMTP timeout\n");
- sx.send_quit = FALSE;
+ sx->send_quit = FALSE;
break;
#ifdef SUPPORT_I18N
break;
#endif
case ECONNREFUSED:
- sx.send_quit = FALSE;
+ sx->send_quit = FALSE;
break;
case 0:
- if (*sx.buffer == 0) Ustrcpy(sx.buffer, US"connection dropped");
+ if (*sx->buffer == 0) Ustrcpy(sx->buffer, US"connection dropped");
/*XXX test here is ugly; seem to have a split of responsibility for
building this message. Need to rationalise. Where is it done
*/
if (!addr->message) addr->message =
string_sprintf("response to \"%s\" was: %s",
- big_buffer, string_printing(sx.buffer));
+ big_buffer, string_printing(sx->buffer));
/* RFC 5321 section 4.2: the text portion of the response may have only
HT, SP, Printable US-ASCII. Deal with awkward chars by cutting the
just become a multiline response (but wrapped in the error code we
produce). */
- for (uschar * s = sx.buffer;
- *s && s < sx.buffer + sizeof(sx.buffer);
+ for (uschar * s = sx->buffer;
+ *s && s < sx->buffer + sizeof(sx->buffer);
s++)
{
uschar c = *s;
if (c != '\t' && c != '\n' && (c < ' ' || c > '~'))
{
- if (s - sx.buffer < sizeof(sx.buffer) - 12)
+ if (s - sx->buffer < sizeof(sx->buffer) - 12)
memcpy(s, "(truncated)", 12);
else
*s = '\0';
}
}
addr->user_message = options & vopt_is_recipient
- ? string_sprintf("Callout verification failed:\n%s", sx.buffer)
+ ? string_sprintf("Callout verification failed:\n%s", sx->buffer)
: string_sprintf("Called: %s\nSent: %s\nResponse: %s",
- host->address, big_buffer, sx.buffer);
+ host->address, big_buffer, sx->buffer);
/* Hard rejection ends the process */
- if (sx.buffer[0] == '5') /* Address rejected */
+ if (sx->buffer[0] == '5') /* Address rejected */
{
yield = FAIL;
done = TRUE;
&& !random_local_part
&& !pm_mailfrom
&& cutthrough.cctx.sock < 0
- && !sx.lmtp
+ && !sx->lmtp
)
{
HDEBUG(D_acl|D_v) debug_printf_indent("holding verify callout open for %s\n",
cutthrough.callout_hold_only = !cutthrough.delivery;
cutthrough.is_tls = tls_out.active.sock >= 0;
/* We assume no buffer in use in the outblock */
- cutthrough.cctx = sx.cctx;
+ cutthrough.cctx = sx->cctx;
cutthrough.nrcpt = 1;
cutthrough.transport = addr->transport->name;
cutthrough.interface = interface;
/* Ensure no cutthrough on multiple verifies that were incompatible */
if (options & vopt_callout_recipsender)
cancel_cutthrough_connection(TRUE, US"not usable for cutthrough");
- if (sx.send_quit)
- if (smtp_write_command(&sx, SCMD_FLUSH, "QUIT\r\n") != -1)
+ if (sx->send_quit)
+ if (smtp_write_command(sx, SCMD_FLUSH, "QUIT\r\n") != -1)
/* Wait a short time for response, and discard it */
- smtp_read_response(&sx, sx.buffer, sizeof(sx.buffer), '2', 1);
+ smtp_read_response(sx, sx->buffer, sizeof(sx->buffer), '2', 1);
- if (sx.cctx.sock >= 0)
+ if (sx->cctx.sock >= 0)
{
#ifndef DISABLE_TLS
- if (sx.cctx.tls_ctx)
+ if (sx->cctx.tls_ctx)
{
- tls_close(sx.cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
- sx.cctx.tls_ctx = NULL;
+ tls_close(sx->cctx.tls_ctx, TLS_SHUTDOWN_NOWAIT);
+ sx->cctx.tls_ctx = NULL;
}
#endif
HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n");
- (void)close(sx.cctx.sock);
- sx.cctx.sock = -1;
+ (void)close(sx->cctx.sock);
+ sx->cctx.sock = -1;
#ifndef DISABLE_EVENT
(void) event_raise(addr->transport->event_action, US"tcp:close", NULL);
#endif
Arguments:
vaddr contains the address to verify; the next field in this block
must be NULL
- f if not NULL, write the result to this file
+ fp if not NULL, write the result to this file
options various option bits:
vopt_fake_sender => this sender verify is not for the real
sender (it was verify=sender=xxxx or an address from a
BOOL success_on_redirect = (options & vopt_success_on_redirect) != 0;
int i;
int yield = OK;
-int verify_type = expn? v_expn :
- f.address_test_mode? v_none :
- options & vopt_is_recipient? v_recipient : v_sender;
+int verify_type = expn ? v_expn :
+ f.address_test_mode ? v_none :
+ options & vopt_is_recipient ? v_recipient : v_sender;
address_item *addr_list;
address_item *addr_new = NULL;
address_item *addr_remote = NULL;
if (rc == OK)
{
+ BOOL local_verify = FALSE;
+
if (routed) *routed = TRUE;
if (callout > 0)
{
transport's options, so as to mimic what would happen if we were really
sending a message to this address. */
- if ((tp = addr->transport) && !tp->info->local)
- {
- (void)(tp->setup)(tp, addr, &tf, 0, 0, NULL);
+ if ((tp = addr->transport))
+ if (!tp->info->local)
+ {
+ (void)(tp->setup)(tp, addr, &tf, 0, 0, NULL);
- /* If the transport has hosts and the router does not, or if the
- transport is configured to override the router's hosts, we must build a
- host list of the transport's hosts, and find the IP addresses */
+ /* If the transport has hosts and the router does not, or if the
+ transport is configured to override the router's hosts, we must build a
+ host list of the transport's hosts, and find the IP addresses */
- if (tf.hosts && (!host_list || tf.hosts_override))
- {
- uschar *s;
- const uschar *save_deliver_domain = deliver_domain;
- uschar *save_deliver_localpart = deliver_localpart;
-
- host_list = NULL; /* Ignore the router's hosts */
-
- deliver_domain = addr->domain;
- deliver_localpart = addr->local_part;
- s = expand_string(tf.hosts);
- deliver_domain = save_deliver_domain;
- deliver_localpart = save_deliver_localpart;
-
- if (!s)
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand list of hosts "
- "\"%s\" in %s transport for callout: %s", tf.hosts,
- tp->name, expand_string_message);
- }
- else
- {
- int flags;
- host_build_hostlist(&host_list, s, tf.hosts_randomize);
-
- /* Just ignore failures to find a host address. If we don't manage
- to find any addresses, the callout will defer. Note that more than
- one address may be found for a single host, which will result in
- additional host items being inserted into the chain. Hence we must
- save the next host first. */
-
- flags = HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
- if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
- if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
-
- for (host_item * host = host_list, * nexthost; host; host = nexthost)
- {
- nexthost = host->next;
- if (tf.gethostbyname ||
- string_is_ip_address(host->name, NULL) != 0)
- (void)host_find_byname(host, NULL, flags, NULL, TRUE);
- else
+ if (tf.hosts && (!host_list || tf.hosts_override))
+ {
+ uschar *s;
+ const uschar *save_deliver_domain = deliver_domain;
+ uschar *save_deliver_localpart = deliver_localpart;
+
+ host_list = NULL; /* Ignore the router's hosts */
+
+ deliver_domain = addr->domain;
+ deliver_localpart = addr->local_part;
+ s = expand_string(tf.hosts);
+ deliver_domain = save_deliver_domain;
+ deliver_localpart = save_deliver_localpart;
+
+ if (!s)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "failed to expand list of hosts "
+ "\"%s\" in %s transport for callout: %s", tf.hosts,
+ tp->name, expand_string_message);
+ }
+ else
+ {
+ int flags;
+ host_build_hostlist(&host_list, s, tf.hosts_randomize);
+
+ /* Just ignore failures to find a host address. If we don't manage
+ to find any addresses, the callout will defer. Note that more than
+ one address may be found for a single host, which will result in
+ additional host items being inserted into the chain. Hence we must
+ save the next host first. */
+
+ flags = HOST_FIND_BY_A | HOST_FIND_BY_AAAA;
+ if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE;
+ if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS;
+
+ for (host_item * host = host_list, * nexthost; host; host = nexthost)
{
- const dnssec_domains * dsp = NULL;
- if (Ustrcmp(tp->driver_name, "smtp") == 0)
+ nexthost = host->next;
+ if (tf.gethostbyname ||
+ string_is_ip_address(host->name, NULL) != 0)
+ (void)host_find_byname(host, NULL, flags, NULL, TRUE);
+ else
{
- smtp_transport_options_block * ob =
- (smtp_transport_options_block *) tp->options_block;
- dsp = &ob->dnssec;
- }
+ const dnssec_domains * dsp = NULL;
+ if (Ustrcmp(tp->driver_name, "smtp") == 0)
+ {
+ smtp_transport_options_block * ob =
+ (smtp_transport_options_block *) tp->options_block;
+ dsp = &ob->dnssec;
+ }
- (void) host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
- dsp, NULL, NULL);
+ (void) host_find_bydns(host, NULL, flags, NULL, NULL, NULL,
+ dsp, NULL, NULL);
+ }
}
- }
- }
- }
- }
+ }
+ }
+ }
+ else if ( options & vopt_quota
+ && Ustrcmp(tp->driver_name, "appendfile") == 0)
+ local_verify = TRUE;
/* Can only do a callout if we have at least one host! If the callout
fails, it will have set ${sender,recipient}_verify_failure. */
#endif
}
}
+ else if (local_verify)
+ {
+ HDEBUG(D_verify) debug_printf("Attempting quota verification\n");
+
+ deliver_set_expansions(addr);
+ deliver_local(addr, TRUE);
+ rc = addr->transport_return;
+ }
else
- {
HDEBUG(D_verify) debug_printf("Cannot do callout: neither router nor "
"transport provided a host list, or transport is not smtp\n");
- }
}
}
addr_list = addr->next;
fprintf(fp, "%s", CS addr->address);
-#ifdef EXPERIMENTAL_SRS
+#ifdef EXPERIMENTAL_SRS_ALT
if(addr->prop.srs_sender)
fprintf(fp, " [srs = %s]", addr->prop.srs_sender);
#endif
colon = Ustrchr(h->text, ':');
s = colon + 1;
- while (isspace(*s)) s++;
+ Uskip_whitespace(&s);
/* Loop for multiple addresses in the header, enabling group syntax. Note
that we have to reset this after the header has been scanned. */
{
if (!f.allow_unqualified_recipient) recipient = NULL;
}
- if (recipient == NULL) errmess = US"unqualified address not permitted";
+ if (!recipient) errmess = US"unqualified address not permitted";
}
/* It's an error if no address could be extracted, except for the special
/* Advance to the next address */
s = ss + (terminator ? 1 : 0);
- while (isspace(*s)) s++;
+ Uskip_whitespace(&s);
} /* Next address */
f.parse_allow_group = FALSE;
colon = Ustrchr(h->text, ':');
s = colon + 1;
- while (isspace(*s)) s++;
+ Uskip_whitespace(&s);
/* Loop for multiple addresses in the header, enabling group syntax. Note
that we have to reset this after the header has been scanned. */
/* Advance to the next address */
s = ss + (terminator ? 1:0);
- while (isspace(*s)) s++;
+ Uskip_whitespace(&s);
} /* Next address */
f.parse_allow_group = FALSE;
/* If we found an empty address, just carry on with the next one, but
kill the message. */
- if (address == NULL && Ustrcmp(*log_msgptr, "empty address") == 0)
+ if (!address && Ustrcmp(*log_msgptr, "empty address") == 0)
{
*log_msgptr = NULL;
s = ss;
function, and ensure that the failing address gets added to the error
message. */
- if (address == NULL)
+ if (!address)
{
new_ok = FAIL;
while (ss > s && isspace(ss[-1])) ss--;
BOOL isquery = FALSE;
BOOL isiponly = cb->host_name != NULL && cb->host_name[0] == 0;
const uschar *t;
-uschar *semicolon;
+uschar * semicolon, * endname, * opts;
uschar **aliases;
/* Optimize for the special case when the pattern is "*". */
local host's IP addresses. */
if (*ss == '@')
- {
if (ss[1] == 0)
{
if (isiponly) return ERROR;
if (Ustrcmp(ip->address, cb->host_address) == 0) return OK;
return FAIL;
}
- }
/* If the pattern is an IP address, optionally followed by a bitmask count, do
a (possibly masked) comparison with the current IP address. */
return ERROR;
}
-/* See if there is a semicolon in the pattern */
+/* See if there is a semicolon in the pattern, separating a searchtype
+prefix. If there is one then check for comma-sep options. */
-semicolon = Ustrchr(ss, ';');
+if ((semicolon = Ustrchr(ss, ';')))
+ if ((opts = Ustrchr(ss, ',')) && opts < semicolon)
+ {
+ endname = opts++;
+ opts = string_copyn(opts, semicolon - opts);
+ }
+ else
+ {
+ endname = semicolon;
+ opts = NULL;
+ }
/* If we are doing an IP address only match, then all lookups must be IP
address lookups, even if there is no "net-". */
if (isiponly)
- {
iplookup = semicolon != NULL;
- }
/* Otherwise, if the item is of the form net[n]-lookup;<file|query> then it is
a lookup on a masked IP network, in textual form. We obey this code even if we
From release 4.30, the use of net- for query style is no longer needed, but we
retain it for backward compatibility. */
-if (Ustrncmp(ss, "net", 3) == 0 && semicolon != NULL)
+if (Ustrncmp(ss, "net", 3) == 0 && semicolon)
{
mlen = 0;
for (t = ss + 3; isdigit(*t); t++) mlen = mlen * 10 + *t - '0';
if (mlen == 0 && t == ss+3) mlen = -1; /* No mask supplied */
- iplookup = (*t++ == '-');
+ iplookup = *t++ == '-';
}
else
t = ss;
/* Find the search type */
- search_type = search_findtype(t, semicolon - t);
+ search_type = search_findtype(t, endname - t);
if (search_type < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s",
search_error_message);
if (!(handle = search_open(filename, search_type, 0, NULL, NULL)))
log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", search_error_message);
- result = search_find(handle, filename, key, -1, NULL, 0, 0, NULL);
+ result = search_find(handle, filename, key, -1, NULL, 0, 0, NULL, opts);
if (valueptr) *valueptr = result;
return result ? OK : f.search_find_defer ? DEFER: FAIL;
}
outgoing hosts, the name is always given explicitly. If it is NULL, it means we
must use sender_host_name and its aliases, looking them up if necessary. */
-if (cb->host_name != NULL) /* Explicit host name given */
+if (cb->host_name) /* Explicit host name given */
return match_check_string(cb->host_name, ss, -1, TRUE, TRUE, TRUE,
valueptr);
$sender_host_name causes it to be looked up, so we don't need to do the lookup
on spec. */
-if ((semicolon = Ustrchr(ss, ';')) != NULL)
+if ((semicolon = Ustrchr(ss, ';')))
{
- const uschar *affix;
+ const uschar * affix, * opts;
int partial, affixlen, starflags, id;
*semicolon = 0;
- id = search_findtype_partial(ss, &partial, &affix, &affixlen, &starflags);
+ id = search_findtype_partial(ss, &partial, &affix, &affixlen, &starflags,
+ &opts);
*semicolon=';';
if (id < 0) /* Unknown lookup type */
tree_node *t;
dnsbl_cache_block *cb;
int old_pool = store_pool;
-uschar query[256]; /* DNS domain max length */
+uschar * query;
+int qlen;
/* Construct the specific query domainname */
-if (!string_format(query, sizeof(query), "%s.%s", prepend, domain))
+query = string_sprintf("%s.%s", prepend, domain);
+if ((qlen = Ustrlen(query)) >= 256)
{
log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long "
"(ignored): %s...", query);
else
{ /* Set up a tree entry to cache the lookup */
- t = store_get(sizeof(tree_node) + Ustrlen(query), is_tainted(query));
+ t = store_get(sizeof(tree_node) + qlen + 1 + 1, is_tainted(query));
Ustrcpy(t->name, query);
t->data.ptr = cb = store_get(sizeof(dnsbl_cache_block), FALSE);
(void)tree_insertnode(&dnsbl_cache, t);
for (da = cb->rhs; da; da = da->next)
{
int ipsep = ',';
- uschar ip[46];
const uschar *ptr = iplist;
uschar *res;
if (!bitmask)
{
- while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))))
- if (Ustrcmp(CS da->address, ip) == 0)
+ while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
+ if (Ustrcmp(CS da->address, res) == 0)
break;
}
/* Scan the returned addresses, skipping any that are IPv6 */
- while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))))
+ while ((res = string_nextinlist(&ptr, &ipsep, NULL, 0)))
{
- if (host_aton(ip, address) != 1) continue;
+ if (host_aton(res, address) != 1) continue;
if ((address[0] & mask) == address[0]) break;
}
}
int defer_return = FAIL;
const uschar *list = *listptr;
uschar *domain;
-uschar buffer[1024];
uschar revadd[128]; /* Long enough for IPv6 address */
/* Indicate that the inverted IP address is not yet set up */
/* Loop through all the domains supplied, until something matches */
-while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))))
+while ((domain = string_nextinlist(&list, &sep, NULL, 0)))
{
int rc;
BOOL bitmask = FALSE;
return FAIL;
}
+
+
+/****************************************************
+ Verify a local user account for quota sufficiency
+****************************************************/
+
+/* The real work, done via a re-exec for privs, calls
+down to the transport for the quota check.
+
+Route and transport (in recipient-verify mode) the
+given recipient.
+
+A routing result indicating any transport type other than appendfile
+results in a fail.
+
+Return, on stdout, a result string containing:
+- highlevel result code (OK, DEFER, FAIL)
+- errno
+- where string
+- message string
+*/
+
+void
+verify_quota(uschar * address)
+{
+address_item vaddr = {.address = address};
+BOOL routed;
+uschar * msg = US"\0";
+int rc, len = 1;
+
+if ((rc = verify_address(&vaddr, NULL, vopt_is_recipient | vopt_quota,
+ 1, 0, 0, NULL, NULL, &routed)) != OK)
+ {
+ uschar * where = recipient_verify_failure;
+ msg = acl_verify_message ? acl_verify_message : vaddr.message;
+ if (!msg) msg = US"";
+ if (rc == DEFER && vaddr.basic_errno == ERRNO_EXIMQUOTA)
+ {
+ rc = FAIL; /* DEFER -> FAIL */
+ where = US"quota";
+ vaddr.basic_errno = 0;
+ }
+ else if (!where) where = US"";
+
+ len = 5 + Ustrlen(msg) + 1 + Ustrlen(where);
+ msg = string_sprintf("%c%c%c%c%c%s%c%s", (uschar)rc,
+ (vaddr.basic_errno >> 24) && 0xff, (vaddr.basic_errno >> 16) && 0xff,
+ (vaddr.basic_errno >> 8) && 0xff, vaddr.basic_errno && 0xff,
+ where, '\0', msg);
+ }
+
+DEBUG(D_verify) debug_printf_indent("verify_quota: len %d\n", len);
+write(1, msg, len);
+return;
+}
+
+
+/******************************************************************************/
+
+/* Quota cache lookup. We use the callout hints db also for the quota cache.
+Return TRUE if a nonexpired record was found, having filled in the yield
+argument.
+*/
+
+static BOOL
+cached_quota_lookup(const uschar * rcpt, int * yield,
+ int pos_cache, int neg_cache)
+{
+open_db dbblock, *dbm_file = NULL;
+dbdata_callout_cache_address * cache_address_record;
+
+if (!pos_cache && !neg_cache)
+ return FALSE;
+if (!(dbm_file = dbfn_open(US"callout", O_RDWR, &dbblock, FALSE, TRUE)))
+ {
+ HDEBUG(D_verify) debug_printf_indent("quota cache: not available\n");
+ return FALSE;
+ }
+if (!(cache_address_record = (dbdata_callout_cache_address *)
+ get_callout_cache_record(dbm_file, rcpt, US"address",
+ pos_cache, neg_cache)))
+ {
+ dbfn_close(dbm_file);
+ return FALSE;
+ }
+if (cache_address_record->result == ccache_accept)
+ *yield = OK;
+dbfn_close(dbm_file);
+return TRUE;
+}
+
+/* Quota cache write */
+
+static void
+cache_quota_write(const uschar * rcpt, int yield, int pos_cache, int neg_cache)
+{
+open_db dbblock, *dbm_file = NULL;
+dbdata_callout_cache_address cache_address_record;
+
+if (!pos_cache && !neg_cache)
+ return;
+if (!(dbm_file = dbfn_open(US"callout", O_RDWR|O_CREAT, &dbblock, FALSE, TRUE)))
+ {
+ HDEBUG(D_verify) debug_printf_indent("quota cache: not available\n");
+ return;
+ }
+
+cache_address_record.result = yield == OK ? ccache_accept : ccache_reject;
+
+(void)dbfn_write(dbm_file, rcpt, &cache_address_record,
+ (int)sizeof(dbdata_callout_cache_address));
+HDEBUG(D_verify) debug_printf_indent("wrote %s quota cache record for %s\n",
+ yield == OK ? "positive" : "negative", rcpt);
+
+dbfn_close(dbm_file);
+return;
+}
+
+
+/* To evaluate a local user's quota, starting in ACL, we need to
+fork & exec to regain privileges, to that we can change to the user's
+identity for access to their files.
+
+Arguments:
+ rcpt Recipient account
+ pos_cache Number of seconds to cache a positive result (delivery
+ to be accepted). Zero to disable caching.
+ neg_cache Number of seconds to cache a negative result. Zero to disable.
+ msg Pointer to result string pointer
+
+Return: OK/DEFER/FAIL code
+*/
+
+int
+verify_quota_call(const uschar * rcpt, int pos_cache, int neg_cache,
+ uschar ** msg)
+{
+int pfd[2], pid, save_errno, yield = FAIL;
+void (*oldsignal)(int);
+const uschar * where = US"socketpair";
+
+*msg = NULL;
+
+if (cached_quota_lookup(rcpt, &yield, pos_cache, neg_cache))
+ {
+ HDEBUG(D_verify) debug_printf_indent("quota cache: address record is %s\n",
+ yield == OK ? "positive" : "negative");
+ if (yield != OK)
+ {
+ recipient_verify_failure = US"quota";
+ acl_verify_message = *msg =
+ US"Previous (cached) quota verification failure";
+ }
+ return yield;
+ }
+
+if (pipe(pfd) != 0)
+ goto fail;
+
+where = US"fork";
+oldsignal = signal(SIGCHLD, SIG_DFL);
+if ((pid = exim_fork(US"quota-verify")) < 0)
+ {
+ save_errno = errno;
+ close(pfd[pipe_write]);
+ close(pfd[pipe_read]);
+ errno = save_errno;
+ goto fail;
+ }
+
+if (pid == 0) /* child */
+ {
+ close(pfd[pipe_read]);
+ force_fd(pfd[pipe_write], 1); /* stdout to pipe */
+ close(pfd[pipe_write]);
+ dup2(1, 0);
+ if (debug_fd > 0) force_fd(debug_fd, 2);
+
+ child_exec_exim(CEE_EXEC_EXIT, FALSE, NULL, FALSE, 3,
+ US"-MCq", string_sprintf("%d", message_size), rcpt);
+ /*NOTREACHED*/
+ }
+
+save_errno = errno;
+close(pfd[pipe_write]);
+
+if (pid < 0)
+ {
+ DEBUG(D_verify) debug_printf_indent(" fork: %s\n", strerror(save_errno));
+ }
+else
+ {
+ uschar buf[128];
+ int n = read(pfd[pipe_read], buf, sizeof(buf));
+ int status;
+
+ waitpid(pid, &status, 0);
+ if (status == 0)
+ {
+ uschar * s;
+
+ if (n > 0) yield = buf[0];
+ if (n > 4)
+ save_errno = (buf[1] << 24) | (buf[2] << 16) | (buf[3] << 8) | buf[4];
+ if ((recipient_verify_failure = n > 5
+ ? string_copyn_taint(buf+5, n-5, FALSE) : NULL))
+ {
+ int m;
+ s = buf + 5 + Ustrlen(recipient_verify_failure) + 1;
+ m = n - (s - buf);
+ acl_verify_message = *msg =
+ m > 0 ? string_copyn_taint(s, m, FALSE) : NULL;
+ }
+
+ DEBUG(D_verify) debug_printf_indent("verify call response:"
+ " len %d yield %s errno '%s' where '%s' msg '%s'\n",
+ n, rc_names[yield], strerror(save_errno), recipient_verify_failure, *msg);
+
+ if ( yield == OK
+ || save_errno == 0 && Ustrcmp(recipient_verify_failure, "quota") == 0)
+ cache_quota_write(rcpt, yield, pos_cache, neg_cache);
+ else DEBUG(D_verify)
+ debug_printf_indent("result not cacheable\n");
+ }
+ else
+ {
+ DEBUG(D_verify)
+ debug_printf_indent("verify call response: waitpid status 0x%04x\n", status);
+ }
+ }
+
+close(pfd[pipe_read]);
+errno = save_errno;
+
+fail:
+
+return yield;
+}
+
+
/* vi: aw ai sw=2
*/
/* End of verify.c */