X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/384152a6b0cce686255894a502e4b88743abd356..d7d7b7b91dd75cec636fc144da7e27eed860f971:/src/src/verify.c diff --git a/src/src/verify.c b/src/src/verify.c index abdb5facc..117cf81f8 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -1,10 +1,10 @@ -/* $Cambridge: exim/src/src/verify.c,v 1.17 2005/05/24 08:15:02 tom Exp $ */ +/* $Cambridge: exim/src/src/verify.c,v 1.31 2006/02/07 11:19:00 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2005 */ +/* Copyright (c) University of Cambridge 1995 - 2006 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions concerned with verifying things. The original code for callout @@ -128,6 +128,7 @@ Arguments: 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 @@ -151,6 +152,7 @@ BOOL done = FALSE; uschar *address_key; uschar *from_address; uschar *random_local_part = NULL; +uschar *save_deliver_domain = deliver_domain; uschar **failure_ptr = is_recipient? &recipient_verify_failure : &sender_verify_failure; open_db dbblock; @@ -408,18 +410,24 @@ for (host = host_list; host != NULL && !done; host = host->next) host_af = (Ustrchr(host->address, ':') == NULL)? AF_INET:AF_INET6; - /* Expand and interpret the interface and port strings. This has to - be delayed till now, because they may expand differently for different - hosts. If there's a failure, log it, but carry on with the defaults. */ + /* Expand and interpret the interface and port strings. The latter will not + be used if there is a host-specific port (e.g. from a manualroute router). + This has to be delayed till now, because they may expand differently for + different hosts. If there's a failure, log it, but carry on with the + defaults. */ deliver_host = host->name; deliver_host_address = host->address; + deliver_domain = addr->domain; + if (!smtp_get_interface(tf->interface, host_af, addr, NULL, &interface, US"callout") || !smtp_get_port(tf->port, addr, &port, US"callout")) log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: %s", addr->address, addr->message); + deliver_host = deliver_host_address = NULL; + deliver_domain = save_deliver_domain; /* Set HELO string according to the protocol */ @@ -536,7 +544,8 @@ for (host = host_list; host != NULL && !done; host = host->next) smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout) && - smtp_write_command(&outblock, FALSE, "MAIL FROM:<>\r\n") >= 0 && + smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n", + from_address) >= 0 && smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout); } @@ -548,10 +557,14 @@ for (host = host_list; host != NULL && !done; host = host->next) if (new_domain_record.random_result != ccache_accept && done) { + /* Get the rcpt_include_affixes flag from the transport if there is one, + but assume FALSE if there is not. */ + done = smtp_write_command(&outblock, FALSE, "RCPT TO:<%.1000s>\r\n", transport_rcpt_address(addr, - addr->transport->rcpt_include_affixes)) >= 0 && + (addr->transport == NULL)? FALSE : + addr->transport->rcpt_include_affixes)) >= 0 && smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout); @@ -563,7 +576,8 @@ for (host = host_list; host != NULL && !done; host = host->next) new_address_record.result = ccache_reject; } - /* Do postmaster check if requested */ + /* Do postmaster check if requested; if a full check is required, we + check for RCPT TO: (no domain) in accordance with RFC 821. */ if (done && pm_mailfrom != NULL) { @@ -577,10 +591,29 @@ for (host = host_list; host != NULL && !done; host = host->next) smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout) && + /* First try using the current domain */ + + (( smtp_write_command(&outblock, FALSE, "RCPT TO:\r\n", addr->domain) >= 0 && smtp_read_response(&inblock, responsebuffer, - sizeof(responsebuffer), '2', callout); + sizeof(responsebuffer), '2', callout) + ) + + || + + /* If that doesn't work, and a full check is requested, + try without the domain. */ + + ( + (options & vopt_callout_fullpm) != 0 && + smtp_write_command(&outblock, FALSE, + "RCPT TO:\r\n") >= 0 && + smtp_read_response(&inblock, responsebuffer, + sizeof(responsebuffer), '2', callout) + )); + + /* Sort out the cache record */ new_domain_record.postmaster_stamp = time(NULL); @@ -594,7 +627,7 @@ for (host = host_list; host != NULL && !done; host = host->next) } } } /* Random not accepted */ - } /* MAIL FROM:<> accepted */ + } /* MAIL FROM: accepted */ /* For any failure of the main check, other than a negative response, we just close the connection and carry on. We can identify a negative response by the @@ -641,7 +674,7 @@ for (host = host_list; host != NULL && !done; host = host->next) /* End the SMTP conversation and close the connection. */ if (send_quit) (void)smtp_write_command(&outblock, FALSE, "QUIT\r\n"); - close(inblock.sock); + (void)close(inblock.sock); } /* Loop through all hosts, while !done */ /* If we get here with done == TRUE, a successful callout happened, and yield @@ -650,7 +683,7 @@ 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, +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 or ccache_reject. */ @@ -787,10 +820,13 @@ Arguments: rewriting and messages from callouts vopt_qualify => qualify an unqualified address; else error vopt_expn => called from SMTP EXPN command + vopt_success_on_redirect => when a new address is generated + the verification instantly succeeds These ones are used by do_callout() -- the options variable is passed to it. + vopt_callout_fullpm => if postmaster check, do full one vopt_callout_no_cache => don't use callout cache vopt_callout_random => do the "random" thing vopt_callout_recipsender => use real sender for recipient @@ -823,6 +859,7 @@ BOOL allok = TRUE; BOOL full_info = (f == NULL)? FALSE : (debug_selector != 0); BOOL is_recipient = (options & vopt_is_recipient) != 0; BOOL expn = (options & vopt_expn) != 0; +BOOL success_on_redirect = (options & vopt_success_on_redirect) != 0; int i; int yield = OK; int verify_type = expn? v_expn : @@ -1023,13 +1060,16 @@ while (addr_new != NULL) if (tf.hosts != NULL && (host_list == NULL || tf.hosts_override)) { uschar *s; + 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 = deliver_localpart = NULL; + deliver_domain = save_deliver_domain; + deliver_localpart = save_deliver_localpart; if (s == NULL) { @@ -1053,7 +1093,7 @@ while (addr_new != NULL) { nexthost = host->next; if (tf.gethostbyname || - string_is_ip_address(host->name, NULL) > 0) + string_is_ip_address(host->name, NULL) != 0) (void)host_find_byname(host, NULL, &canonical_name, TRUE); else { @@ -1192,9 +1232,12 @@ while (addr_new != NULL) generated address. */ if (!full_info && /* Stop if short info wanted AND */ - (addr_new == NULL || /* No new address OR */ - addr_new->next != NULL || /* More than one new address OR */ - testflag(addr_new, af_pfr))) /* New address is pfr */ + (((addr_new == NULL || /* No new address OR */ + addr_new->next != NULL || /* More than one new address OR */ + testflag(addr_new, af_pfr))) /* New address is pfr */ + || /* OR */ + (addr_new != NULL && /* At least one new address AND */ + success_on_redirect))) /* success_on_redirect is set */ { if (f != NULL) fprintf(f, "%s %s\n", address, address_test_mode? "is deliverable" : "verified"); @@ -1412,6 +1455,89 @@ return OK; +/************************************************* +* Check for blind recipients * +*************************************************/ + +/* This function checks that every (envelope) recipient is mentioned in either +the To: or Cc: header lines, thus detecting blind carbon copies. + +There are two ways of scanning that could be used: either scan the header lines +and tick off the recipients, or scan the recipients and check the header lines. +The original proposed patch did the former, but I have chosen to do the latter, +because (a) it requires no memory and (b) will use fewer resources when there +are many addresses in To: and/or Cc: and only one or two envelope recipients. + +Arguments: none +Returns: OK if there are no blind recipients + FAIL if there is at least one blind recipient +*/ + +int +verify_check_notblind(void) +{ +int i; +for (i = 0; i < recipients_count; i++) + { + header_line *h; + BOOL found = FALSE; + uschar *address = recipients_list[i].address; + + for (h = header_list; !found && h != NULL; h = h->next) + { + uschar *colon, *s; + + if (h->type != htype_to && h->type != htype_cc) continue; + + colon = Ustrchr(h->text, ':'); + s = colon + 1; + while (isspace(*s)) s++; + + parse_allow_group = TRUE; /* Allow group syntax */ + + /* Loop for multiple addresses in the header */ + + while (*s != 0) + { + uschar *ss = parse_find_address_end(s, FALSE); + uschar *recipient,*errmess; + int terminator = *ss; + int start, end, domain; + + /* Temporarily terminate the string at this point, and extract the + operative address within. */ + + *ss = 0; + recipient = parse_extract_address(s,&errmess,&start,&end,&domain,FALSE); + *ss = terminator; + + /* If we found a valid recipient that has a domain, compare it with the + envelope recipient. Local parts are compared case-sensitively, domains + case-insensitively. By comparing from the start with length "domain", we + include the "@" at the end, which ensures that we are comparing the whole + local part of each address. */ + + if (recipient != NULL && domain != 0) + { + found = Ustrncmp(recipient, address, domain) == 0 && + strcmpic(recipient + domain, address + domain) == 0; + if (found) break; + } + + /* Advance to the next address */ + + s = ss + (terminator? 1:0); + while (isspace(*s)) s++; + } /* Next address */ + } /* Next header (if found is false) */ + + if (!found) return FAIL; + } /* Next recipient */ + +return OK; +} + + /************************************************* * Find if verified sender * @@ -1770,7 +1896,7 @@ sender_ident = string_printing(string_copyn(p, 127)); DEBUG(D_ident) debug_printf("sender_ident = %s\n", sender_ident); END_OFF: -close(sock); +(void)close(sock); return; } @@ -1793,25 +1919,34 @@ Arguments: error for error message when returning ERROR The block contains: - host_name the host name or NULL, implying use sender_host_name and - sender_host_aliases, looking them up if required + host_name (a) the host name, or + (b) NULL, implying use sender_host_name and + sender_host_aliases, looking them up if required, or + (c) the empty string, meaning that only IP address matches + are permitted host_address the host address host_ipv4 the IPv4 address taken from an IPv6 one Returns: OK matched FAIL did not match DEFER lookup deferred - ERROR failed to find the host name or IP address - unknown lookup type specified + ERROR (a) failed to find the host name or IP address, or + (b) unknown lookup type specified, or + (c) host name encountered when only IP addresses are + being matched */ -static int +int check_host(void *arg, uschar *ss, uschar **valueptr, uschar **error) { check_host_block *cb = (check_host_block *)arg; +int mlen = -1; int maskoffset; +BOOL iplookup = FALSE; BOOL isquery = FALSE; -uschar *semicolon, *t; +BOOL isiponly = cb->host_name != NULL && cb->host_name[0] == 0; +uschar *t = ss; +uschar *semicolon; uschar **aliases; /* Optimize for the special case when the pattern is "*". */ @@ -1825,12 +1960,17 @@ 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 the pattern is precisely "@" then match against the primary host name; -if it's "@[]" match against the local host's IP addresses. */ +/* 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 +local host's IP addresses. */ if (*ss == '@') { - if (ss[1] == 0) ss = primary_hostname; + if (ss[1] == 0) + { + if (isiponly) return ERROR; + ss = primary_hostname; + } else if (Ustrcmp(ss, "@[]") == 0) { ip_address_item *ip; @@ -1843,76 +1983,109 @@ if (*ss == '@') /* If the pattern is an IP address, optionally followed by a bitmask count, do a (possibly masked) comparision with the current IP address. */ -if (string_is_ip_address(ss, &maskoffset) > 0) +if (string_is_ip_address(ss, &maskoffset) != 0) return (host_is_in_net(cb->host_address, ss, maskoffset)? OK : FAIL); -/* If the item is of the form net[n]-lookup; then it is a lookup on -a masked IP network, in textual form. The net- stuff really only applies to -single-key lookups where the key is implicit. For query-style lookups the key -is specified in the query. From release 4.30, the use of net- for query style -is no longer needed, but we retain it for backward compatibility. */ +/* See if there is a semicolon in the pattern */ + +semicolon = Ustrchr(ss, ';'); -if (Ustrncmp(ss, "net", 3) == 0 && (semicolon = Ustrchr(ss, ';')) != 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) { - int mlen = 0; - for (t = ss + 3; isdigit(*t); t++) mlen = mlen * 10 + *t - '0'; - if (*t++ == '-') - { - int insize; - int search_type; - int incoming[4]; - void *handle; - uschar *filename, *key, *result; - uschar buffer[64]; + iplookup = semicolon != NULL; + } - /* If no mask was supplied, set a negative value */ +/* Otherwise, if the item is of the form net[n]-lookup; then it is +a lookup on a masked IP network, in textual form. We obey this code even if we +have already set iplookup, so as to skip over the "net-" prefix and to set the +mask length. The net- stuff really only applies to single-key lookups where the +key is implicit. For query-style lookups the key is specified in the query. +From release 4.30, the use of net- for query style is no longer needed, but we +retain it for backward compatibility. */ - if (mlen == 0 && t == ss+4) mlen = -1; +if (Ustrncmp(ss, "net", 3) == 0 && semicolon != NULL) + { + 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++ == '-'); + } - /* Find the search type */ +/* Do the IP address lookup if that is indeed what we have */ - search_type = search_findtype(t, semicolon - t); +if (iplookup) + { + int insize; + int search_type; + int incoming[4]; + void *handle; + uschar *filename, *key, *result; + uschar buffer[64]; - if (search_type < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", - search_error_message); + /* Find the search type */ - /* Adjust parameters for the type of lookup. For a query-style - lookup, there is no file name, and the "key" is just the query. For - a single-key lookup, the key is the current IP address, masked - appropriately, and reconverted to text form, with the mask appended. - For IPv6 addresses, specify dot separators instead of colons. */ + search_type = search_findtype(t, semicolon - t); - if (mac_islookup(search_type, lookup_querystyle)) - { - filename = NULL; - key = semicolon + 1; - } - else - { - insize = host_aton(cb->host_address, incoming); - host_mask(insize, incoming, mlen); - (void)host_nmtoa(insize, incoming, mlen, buffer, '.'); - key = buffer; - filename = semicolon + 1; - } + if (search_type < 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", + search_error_message); - /* Now do the actual lookup; note that there is no search_close() because - of the caching arrangements. */ + /* Adjust parameters for the type of lookup. For a query-style lookup, there + is no file name, and the "key" is just the query. For query-style with a file + name, we have to fish the file off the start of the query. For a single-key + lookup, the key is the current IP address, masked appropriately, and + reconverted to text form, with the mask appended. For IPv6 addresses, specify + dot separators instead of colons. */ - handle = search_open(filename, search_type, 0, NULL, NULL); - if (handle == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", - search_error_message); - result = search_find(handle, filename, key, -1, NULL, 0, 0, NULL); - if (valueptr != NULL) *valueptr = result; - return (result != NULL)? OK : search_find_defer? DEFER: FAIL; + if (mac_islookup(search_type, lookup_absfilequery)) + { + filename = semicolon + 1; + key = filename; + while (*key != 0 && !isspace(*key)) key++; + filename = string_copyn(filename, key - filename); + while (isspace(*key)) key++; } + else if (mac_islookup(search_type, lookup_querystyle)) + { + filename = NULL; + key = semicolon + 1; + } + else + { + insize = host_aton(cb->host_address, incoming); + host_mask(insize, incoming, mlen); + (void)host_nmtoa(insize, incoming, mlen, buffer, '.'); + key = buffer; + filename = semicolon + 1; + } + + /* Now do the actual lookup; note that there is no search_close() because + of the caching arrangements. */ + + handle = search_open(filename, search_type, 0, NULL, NULL); + if (handle == NULL) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", + search_error_message); + result = search_find(handle, filename, key, -1, NULL, 0, 0, NULL); + if (valueptr != NULL) *valueptr = result; + return (result != NULL)? OK : search_find_defer? DEFER: FAIL; } /* The pattern is not an IP address or network reference of any kind. That is, -it is a host name pattern. Check the characters of the pattern to see if they -comprise only letters, 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. */ +it is a host name pattern. If this is an IP only match, there's an error in the +host list. */ + +if (isiponly) + { + *error = US"cannot match host name in match_ip list"; + return ERROR; + } + +/* Check the characters of the pattern to see if they comprise only letters, +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++) if (!isalnum(*t) && *t != '.' && *t != '-' && *t != '_' && @@ -1936,9 +2109,7 @@ if (*t == 0) host_item *hh; for (hh = &h; hh != NULL; hh = hh->next) { - if (Ustrcmp(hh->address, (Ustrchr(hh->address, ':') == NULL)? - cb->host_ipv4 : cb->host_address) == 0) - return OK; + if (host_is_in_net(hh->address, cb->host_address, 0)) return OK; } return FAIL; } @@ -1977,7 +2148,7 @@ if ((semicolon = Ustrchr(ss, ';')) != NULL) search_error_message, ss); return DEFER; } - isquery = mac_islookup(id, lookup_querystyle); + isquery = mac_islookup(id, lookup_querystyle|lookup_absfilequery); } if (isquery) @@ -2613,7 +2784,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL while ((keydomain = string_nextinlist(&key, &keysep, keybuffer, sizeof(keybuffer))) != NULL) { - if (string_is_ip_address(keydomain, NULL) > 0) + if (string_is_ip_address(keydomain, NULL) != 0) { uschar keyrevadd[128]; invert_address(keyrevadd, keydomain);