X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/b07d141af23f2ab160eba2b58a834baee513b3f8..b0e63c7efdc2133c61545b051042d3617ecd2bbd:/src/src/verify.c diff --git a/src/src/verify.c b/src/src/verify.c index a5c6de576..194e9a76a 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). */ @@ -78,7 +79,7 @@ if (type[0] == 'd' && cache_record->result != ccache_reject) { if (length == sizeof(dbdata_callout_cache_obs)) { - dbdata_callout_cache *new = store_get(sizeof(dbdata_callout_cache), FALSE); + dbdata_callout_cache * new = store_get(sizeof(dbdata_callout_cache), GET_UNTAINTED); memcpy(new, cache_record, length); new->postmaster_stamp = new->random_stamp = new->time_stamp; cache_record = new; @@ -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. @@ -400,7 +401,7 @@ if (addr->transport == cutthrough.addr.transport) if (done) { - address_item * na = store_get(sizeof(address_item), FALSE); + address_item * na = store_get(sizeof(address_item), GET_UNTAINTED); *na = cutthrough.addr; cutthrough.addr = *addr; cutthrough.addr.host_used = &cutthrough.host; @@ -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; @@ -651,7 +667,7 @@ coding means skipping this whole loop and doing the append separately. */ log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address, addr->message); - if (!sx) sx = store_get(sizeof(*sx), TRUE); /* tainted buffers */ + if (!sx) sx = store_get(sizeof(*sx), GET_TAINTED); /* tainted buffers */ memset(sx, 0, sizeof(*sx)); sx->addrlist = sx->first_addr = addr; @@ -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; @@ -1096,7 +1139,7 @@ no_conn: for (address_item * caddr = &cutthrough.addr, * parent = addr->parent; parent; caddr = caddr->parent, parent = parent->parent) - *(caddr->parent = store_get(sizeof(address_item), FALSE)) = *parent; + *(caddr->parent = store_get(sizeof(address_item), GET_UNTAINTED)) = *parent; ctctx.outblock.buffer = ctbuffer; ctctx.outblock.buffersize = sizeof(ctbuffer); @@ -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; @@ -2872,28 +2928,27 @@ Returns: OK matched */ int -check_host(void *arg, const uschar *ss, const uschar **valueptr, uschar **error) +check_host(void * arg, const uschar * ss, const uschar ** valueptr, uschar ** error) { -check_host_block *cb = (check_host_block *)arg; +check_host_block * cb = (check_host_block *)arg; int mlen = -1; int maskoffset; -BOOL iplookup = FALSE; -BOOL isquery = FALSE; -BOOL isiponly = cb->host_name != NULL && cb->host_name[0] == 0; -const uschar *t; +BOOL iplookup = FALSE, isquery = FALSE; +BOOL isiponly = cb->host_name && !cb->host_name[0]; +const uschar * t; uschar * semicolon, * endname, * opts; -uschar **aliases; +uschar ** aliases; /* Optimize for the special case when the pattern is "*". */ -if (*ss == '*' && ss[1] == 0) return OK; +if (*ss == '*' && !ss[1]) return OK; /* If the pattern is empty, it matches only in the case when there is no host - this can occur in ACL checking for SMTP input using the -bs option. In this situation, the host address is the empty string. */ -if (cb->host_address[0] == 0) return (*ss == 0)? OK : FAIL; -if (*ss == 0) return FAIL; +if (!cb->host_address[0]) return *ss ? FAIL : OK; +if (!*ss) return FAIL; /* If the pattern is precisely "@" then match against the primary host name, provided that host name matching is permitted; if it's "@[]" match against the @@ -2916,7 +2971,7 @@ if (*ss == '@') a (possibly masked) comparison with the current IP address. */ if (string_is_ip_address(ss, &maskoffset) != 0) - return (host_is_in_net(cb->host_address, ss, maskoffset)? OK : FAIL); + return host_is_in_net(cb->host_address, ss, maskoffset) ? OK : FAIL; /* The pattern is not an IP address. A common error that people make is to omit one component of an IPv4 address, either by accident, or believing that, for @@ -2927,13 +2982,25 @@ ancient specification.) To aid in debugging these cases, we give a specific error if the pattern contains only digits and dots or contains a slash preceded only by digits and dots (a slash at the start indicates a file name and of course slashes may be present in lookups, but not preceded only by digits and -dots). */ +dots). Then the equivalent for IPv6 (roughly). */ -for (t = ss; isdigit(*t) || *t == '.'; ) t++; -if (*t == 0 || (*t == '/' && t != ss)) +if (Ustrchr(ss, ':')) { - *error = US"malformed IPv4 address or address mask"; - return ERROR; + for (t = ss; isxdigit(*t) || *t == ':' || *t == '.'; ) t++; + if (!*t || (*t == '/' || *t == '%') && t != ss) + { + *error = string_sprintf("malformed IPv6 address or address mask: %.*s", (int)(t - ss), ss); + return ERROR; + } + } +else + { + for (t = ss; isdigit(*t) || *t == '.'; ) t++; + if (!*t || (*t == '/' && t != ss)) + { + *error = string_sprintf("malformed IPv4 address or address mask: %.*s", (int)(t - ss), ss); + return ERROR; + } } /* See if there is a semicolon in the pattern, separating a searchtype @@ -2950,6 +3017,8 @@ if ((semicolon = Ustrchr(ss, ';'))) endname = semicolon; opts = NULL; } +else + 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-". */ @@ -3051,7 +3120,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; @@ -3059,7 +3128,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; @@ -3090,8 +3159,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 @@ -3120,7 +3189,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; @@ -3146,7 +3217,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; @@ -3156,7 +3229,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; @@ -3232,8 +3307,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; @@ -3545,13 +3620,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:"