X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/4191cb150300d310ab5fa22ce2cfb02b6f6051b0..fa1c8faf169384bebaa8d172f491574c45ae2aa4:/src/src/verify.c diff --git a/src/src/verify.c b/src/src/verify.c index d78b8bf24..bc3666285 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -2,9 +2,10 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2018 */ -/* Copyright (c) The Exim Maintainers 2020 - 2021 */ +/* Copyright (c) The Exim Maintainers 2020 - 2023 */ +/* Copyright (c) University of Cambridge 1995 - 2023 */ /* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* Functions concerned with verifying things. The original code for callout caching was contributed by Kevin Fleming (but I hacked it around a bit). */ @@ -104,8 +105,8 @@ Return: TRUE if result found */ static BOOL -cached_callout_lookup(address_item * addr, uschar * address_key, - uschar * from_address, int * opt_ptr, uschar ** pm_ptr, +cached_callout_lookup(address_item * addr, const uschar * address_key, + const uschar * from_address, int * opt_ptr, uschar ** pm_ptr, int * yield, uschar ** failure_ptr, dbdata_callout_cache * new_domain_record, int * old_domain_res) { @@ -277,10 +278,10 @@ return FALSE; */ static void cache_callout_write(dbdata_callout_cache * dom_rec, const uschar * domain, - int done, dbdata_callout_cache_address * addr_rec, uschar * address_key) + int done, dbdata_callout_cache_address * addr_rec, const uschar * address_key) { open_db dbblock; -open_db *dbm_file = NULL; +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. @@ -445,6 +446,21 @@ return done; } + + +/* A rcpt callout, or cached record of one, verified the address. +Set $domain_data and $local_part_data to detainted versions. +*/ +static void +callout_verified_rcpt(const address_item * addr) +{ +address_item a = {.address = addr->address}; +if (deliver_split_address(&a) != OK) return; +deliver_localpart_data = string_copy_taint(a.local_part, GET_UNTAINTED); +deliver_domain_data = string_copy_taint(a.domain, GET_UNTAINTED); +} + + /************************************************* * Do callout verification for an address * *************************************************/ @@ -486,11 +502,11 @@ do_callout(address_item *addr, host_item *host_list, transport_feedback *tf, 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 +const uschar * address_key; +const 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; @@ -661,6 +677,7 @@ coding means skipping this whole loop and doing the append separately. */ sx->conn_args.interface = interface; sx->helo_data = tf->helo_data; sx->conn_args.tblock = addr->transport; + sx->cctx.sock = sx->conn_args.sock = -1; sx->verify = TRUE; tls_retry_connection: @@ -692,6 +709,30 @@ tls_retry_connection: if (yield != OK) { errno = addr->basic_errno; + + /* For certain errors we want specifically to log the transport name, + for ease of fixing config errors. Slightly ugly doing it here, but we want + to not leak that also in the SMTP response. */ + switch (errno) + { + case EPROTOTYPE: + case ENOPROTOOPT: + case EPROTONOSUPPORT: + case ESOCKTNOSUPPORT: + case EOPNOTSUPP: + case EPFNOSUPPORT: + case EAFNOSUPPORT: + case EADDRINUSE: + case EADDRNOTAVAIL: + case ENETDOWN: + case ENETUNREACH: + log_write(0, LOG_MAIN|LOG_PANIC, + "%s verify %s (making calloout connection): T=%s %s", + options & vopt_is_recipient ? "sender" : "recipient", + yield == FAIL ? "fail" : "defer", + transport_name, strerror(errno)); + } + transport_name = NULL; deliver_host = deliver_host_address = NULL; deliver_domain = save_deliver_domain; @@ -730,7 +771,7 @@ tls_retry_connection: if (random_local_part) { - uschar * main_address = addr->address; + const uschar * main_address = addr->address; const uschar * rcpt_domain = addr->domain; #ifdef SUPPORT_I18N @@ -901,7 +942,7 @@ tls_retry_connection: if (done) { - uschar * main_address = addr->address; + const uschar * main_address = addr->address; /*XXX oops, affixes */ addr->address = string_sprintf("postmaster@%.1000s", addr->domain); @@ -1039,6 +1080,8 @@ no_conn: HDEBUG(D_acl|D_v) debug_printf("Cutthrough cancelled by presence of transport filter\n"); } #ifndef DISABLE_DKIM + /* DKIM signing needs to add a header after seeing the whole body, so we cannot just copy + body bytes to the outbound as they are received, which is the intent of cutthrough. */ if (ob->dkim.dkim_domain) { cutthrough.delivery= FALSE; @@ -1109,7 +1152,7 @@ no_conn: /* 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 (sx->send_quit && sx->cctx.sock >= 0) 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); @@ -1126,6 +1169,7 @@ no_conn: HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n"); (void)close(sx->cctx.sock); sx->cctx.sock = -1; + smtp_debug_cmd_report(); #ifndef DISABLE_EVENT (void) event_raise(addr->transport->event_action, US"tcp:close", NULL, NULL); #endif @@ -1242,7 +1286,7 @@ return FALSE; static BOOL -_cutthrough_puts(uschar * cp, int n) +_cutthrough_puts(const uschar * cp, int n) { while(n--) { @@ -1257,7 +1301,7 @@ return TRUE; /* Buffered output of counted data block. Return boolean success */ static BOOL -cutthrough_puts(uschar * cp, int n) +cutthrough_puts(const uschar * cp, int n) { if (cutthrough.cctx.sock < 0) return TRUE; if (_cutthrough_puts(cp, n)) return TRUE; @@ -1309,7 +1353,13 @@ cutthrough_data_puts(US"\r\n", 2); } -/* Get and check response from cutthrough target */ +/* Get and check response from cutthrough target. +Used for +- nonfirst RCPT +- predata +- data finaldot +- cutthrough conn close +*/ static uschar cutthrough_response(client_conn_ctx * cctx, char expect, uschar ** copy, int timeout) { @@ -1323,7 +1373,7 @@ sx.inblock.ptr = inbuffer; sx.inblock.ptrend = inbuffer; sx.inblock.cctx = cctx; if(!smtp_read_response(&sx, responsebuffer, sizeof(responsebuffer), expect, timeout)) - cancel_cutthrough_connection(TRUE, US"target timeout on read"); + cancel_cutthrough_connection(TRUE, US"unexpected response to smtp command"); if(copy) { @@ -1346,7 +1396,7 @@ cutthrough_predata(void) if(cutthrough.cctx.sock < 0 || cutthrough.callout_hold_only) return FALSE; -HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> DATA\n"); +smtp_debug_cmd(US"DATA", 0); cutthrough_puts(US"DATA\r\n", 6); cutthrough_flush_send(); @@ -1357,9 +1407,9 @@ return cutthrough_response(&cutthrough.cctx, '3', NULL, CUTTHROUGH_DATA_TIMEOUT) /* tctx arg only to match write_chunk() */ static BOOL -cutthrough_write_chunk(transport_ctx * tctx, uschar * s, int len) +cutthrough_write_chunk(transport_ctx * tctx, const uschar * s, int len) { -uschar * s2; +const uschar * s2; while(s && (s2 = Ustrchr(s, '\n'))) { if(!cutthrough_puts(s, s2-s) || !cutthrough_put_nl()) @@ -1414,7 +1464,7 @@ if(fd >= 0) */ client_conn_ctx tmp_ctx = cutthrough.cctx; ctctx.outblock.ptr = ctbuffer; - HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP>> QUIT\n"); + smtp_debug_cmd(US"QUIT", 0); _cutthrough_puts(US"QUIT\r\n", 6); /* avoid recursion */ _cutthrough_flush_send(); cutthrough.cctx.sock = -1; /* avoid recursion via read timeout */ @@ -1433,6 +1483,7 @@ if(fd >= 0) #endif HDEBUG(D_transport|D_acl|D_v) debug_printf_indent(" SMTP(close)>>\n"); (void)close(fd); + smtp_debug_cmd_report(); HDEBUG(D_acl) debug_printf_indent("----------- cutthrough shutdown (%s) ------------\n", why); } ctctx.outblock.ptr = ctbuffer; @@ -1646,16 +1697,16 @@ int yield = OK; 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; -address_item *addr_local = NULL; -address_item *addr_succeed = NULL; -uschar **failure_ptr = options & vopt_is_recipient +address_item * addr_list; +address_item * addr_new = NULL; +address_item * addr_remote = NULL; +address_item * addr_local = NULL; +address_item * addr_succeed = NULL; +uschar ** failure_ptr = options & vopt_is_recipient ? &recipient_verify_failure : &sender_verify_failure; -uschar *ko_prefix, *cr; -uschar *address = vaddr->address; -uschar *save_sender; +uschar * ko_prefix, * cr; +const uschar * address = vaddr->address; +const uschar * save_sender; uschar null_sender[] = { 0 }; /* Ensure writeable memory */ /* Clear, just in case */ @@ -1700,9 +1751,8 @@ may have been set by domains and local part tests during an ACL. */ if (global_rewrite_rules) { - uschar *old = address; - /* deconst ok as address was not const */ - address = US rewrite_address(address, options & vopt_is_recipient, FALSE, + const uschar * old = address; + address = rewrite_address(address, options & vopt_is_recipient, FALSE, global_rewrite_rules, rewrite_existflags); if (address != old) { @@ -1868,8 +1918,8 @@ while (addr_new) if (tf.hosts && (!host_list || tf.hosts_override)) { uschar *s; - const uschar *save_deliver_domain = deliver_domain; - uschar *save_deliver_localpart = deliver_localpart; + const uschar * save_deliver_domain = deliver_domain; + const uschar * save_deliver_localpart = deliver_localpart; host_list = NULL; /* Ignore the router's hosts */ @@ -1949,6 +1999,12 @@ while (addr_new) #ifndef DISABLE_TLS deliver_set_expansions(NULL); #endif + if ( options & vopt_is_recipient + && rc == OK + /* set to "random", with OK, for an accepted random */ + && !recipient_verify_failure + ) + callout_verified_rcpt(addr); } } else if (local_verify) @@ -2398,11 +2454,11 @@ verify_check_notblind(BOOL case_sensitive) for (int i = 0; i < recipients_count; i++) { BOOL found = FALSE; - uschar *address = recipients_list[i].address; + const uschar * address = recipients_list[i].address; for (header_line * h = header_list; !found && h; h = h->next) { - uschar *colon, *s; + uschar * colon, * s; if (h->type != htype_to && h->type != htype_cc) continue; @@ -2476,7 +2532,7 @@ Returns: pointer to an address item, or NULL */ address_item * -verify_checked_sender(uschar *sender) +verify_checked_sender(const uschar * sender) { for (address_item * addr = sender_verified_list; addr; addr = addr->next) if (Ustrcmp(sender, addr->address) == 0) return addr; @@ -2931,7 +2987,7 @@ dots). */ for (t = ss; isdigit(*t) || *t == '.'; ) t++; if (!*t || (*t == '/' && t != ss)) { - *error = US"malformed IPv4 address or address mask"; + *error = string_sprintf("malformed IPv4 address or address mask: %.*s", (int)(t - ss), ss); return ERROR; } @@ -3050,7 +3106,7 @@ digits, full stops, and hyphens (the constituents of domain names). Allow underscores, as they are all too commonly found. Sigh. Also, if allow_utf8_domains is set, allow top-bit characters. */ -for (t = ss; *t != 0; t++) +for (t = ss; *t; t++) if (!isalnum(*t) && *t != '.' && *t != '-' && *t != '_' && (!allow_utf8_domains || *t < 128)) break; @@ -3058,7 +3114,7 @@ for (t = ss; *t != 0; t++) its IP address and match against that. Note that a multi-homed host will add items to the chain. */ -if (*t == 0) +if (!*t) { int rc; host_item h; @@ -3089,8 +3145,8 @@ 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) /* Explicit host name given */ - return match_check_string(cb->host_name, ss, -1, TRUE, TRUE, TRUE, - valueptr); + return match_check_string(cb->host_name, ss, -1, + MCS_PARTIAL | MCS_CASELESS | MCS_AT_SPECIAL | cb->flags, valueptr); /* Host name not given; in principle we need the sender host name and its aliases. However, for query-style lookups, we do not need the name if the @@ -3119,7 +3175,9 @@ if ((semicolon = Ustrchr(ss, ';'))) if (isquery) { - switch(match_check_string(US"", ss, -1, TRUE, TRUE, TRUE, valueptr)) + switch(match_check_string(US"", ss, -1, + MCS_PARTIAL| MCS_CASELESS| MCS_AT_SPECIAL | (cb->flags & MCS_CACHEABLE), + valueptr)) { case OK: return OK; case DEFER: return DEFER; @@ -3145,7 +3203,9 @@ if (!sender_host_name) /* Match on the sender host name, using the general matching function */ -switch(match_check_string(sender_host_name, ss, -1, TRUE, TRUE, TRUE, valueptr)) +switch(match_check_string(sender_host_name, ss, -1, + MCS_PARTIAL| MCS_CASELESS| MCS_AT_SPECIAL | (cb->flags & MCS_CACHEABLE), + valueptr)) { case OK: return OK; case DEFER: return DEFER; @@ -3155,7 +3215,9 @@ switch(match_check_string(sender_host_name, ss, -1, TRUE, TRUE, TRUE, valueptr)) aliases = sender_host_aliases; while (*aliases) - switch(match_check_string(*aliases++, ss, -1, TRUE, TRUE, TRUE, valueptr)) + switch(match_check_string(*aliases++, ss, -1, + MCS_PARTIAL| MCS_CASELESS| MCS_AT_SPECIAL | (cb->flags & MCS_CACHEABLE), + valueptr)) { case OK: return OK; case DEFER: return DEFER; @@ -3231,8 +3293,8 @@ rc = match_check_list( check_host, /* function for testing */ &cb, /* argument for function */ MCL_HOST, /* type of check */ - (host_address == sender_host_address)? - US"host" : host_address, /* text for debugging */ + host_address == sender_host_address + ? US"host" : host_address, /* text for debugging */ valueptr); /* where to pass back data */ deliver_host_address = save_host_address; return rc; @@ -3544,13 +3606,13 @@ else 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)) + ? string_copyn_taint(buf+5, n-5, GET_UNTAINTED) : 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; + m > 0 ? string_copyn_taint(s, m, GET_UNTAINTED) : NULL; } DEBUG(D_verify) debug_printf_indent("verify call response:"