X-Git-Url: https://git.exim.org/users/jgh/exim.git/blobdiff_plain/1688f43b3071b3b4d7d3a88a6ccf28c1bc3272e0..ccee17956fb0d611dd5f45adb6ba31afc4eafd01:/src/src/verify.c diff --git a/src/src/verify.c b/src/src/verify.c index ef7ab8d22..475f52d92 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -1,10 +1,8 @@ -/* $Cambridge: exim/src/src/verify.c,v 1.32 2006/02/13 11:13:37 ph10 Exp $ */ - /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2006 */ +/* Copyright (c) University of Cambridge 1995 - 2009 */ /* See the file NOTICE for conditions of use and distribution. */ /* Functions concerned with verifying things. The original code for callout @@ -29,6 +27,12 @@ typedef struct dnsbl_cache_block { static tree_node *dnsbl_cache = NULL; +/* Bits for match_type in one_check_dnsbl() */ + +#define MT_NOT 1 +#define MT_ALL 2 + + /************************************************* * Retrieve a callout cache record * @@ -148,6 +152,7 @@ BOOL callout_no_cache = (options & vopt_callout_no_cache) != 0; BOOL callout_random = (options & vopt_callout_random) != 0; int yield = OK; +int old_domain_cache_result = ccache_accept; BOOL done = FALSE; uschar *address_key; uschar *from_address; @@ -228,10 +233,18 @@ if (dbm_file != NULL) if (cache_record != NULL) { - /* If an early command (up to and including MAIL FROM:<>) was rejected, - there is no point carrying on. The callout fails. */ - - if (cache_record->result == ccache_reject) + /* In most cases, if an early command (up to and including MAIL FROM:<>) + was rejected, there is no point carrying on. The callout fails. However, if + we are doing a recipient verification with use_sender or use_postmaster + set, a previous failure of MAIL FROM:<> doesn't count, because this time we + will be using a non-empty sender. We have to remember this situation so as + 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; + + if (cache_record->result == ccache_reject || + (*from_address == 0 && cache_record->result == ccache_reject_mfnull)) { setflag(addr, af_verify_nsfail); HDEBUG(D_verify) @@ -370,6 +383,14 @@ if (callout_overall < 0) callout_overall = 4 * callout; if (callout_connect < 0) callout_connect = callout; callout_start_time = time(NULL); +/* Before doing a real callout, if this is an SMTP connection, flush the SMTP +output because a callout might take some time. When PIPELINING is active and +there are many recipients, the total time for doing lots of callouts can add up +and cause the client to time out. So in this case we forgo the PIPELINING +optimization. */ + +if (smtp_out != NULL && !disable_callout_flush) mac_smtp_fflush(); + /* Now make connections to the hosts and do real callouts. The list of hosts is passed in as an argument. */ @@ -380,6 +401,7 @@ for (host = host_list; host != NULL && !done; host = host->next) int host_af; int port = 25; BOOL send_quit = TRUE; + uschar *active_hostname = smtp_active_hostname; uschar *helo = US"HELO"; uschar *interface = NULL; /* Outgoing interface to use; NULL => any */ uschar inbuffer[4096]; @@ -426,9 +448,6 @@ for (host = host_list; host != NULL && !done; host = host->next) 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 */ if (Ustrcmp(tf->protocol, "lmtp") == 0) helo = US"LHLO"; @@ -459,53 +478,93 @@ for (host = host_list; host != NULL && !done; host = host->next) { addr->message = string_sprintf("could not connect to %s [%s]: %s", host->name, host->address, strerror(errno)); + deliver_host = deliver_host_address = NULL; + deliver_domain = save_deliver_domain; continue; } - /* Wait for initial response, and then run the initial SMTP commands. The - smtp_write_command() function leaves its command in big_buffer. This is - used in error responses. Initialize it in case the connection is - rejected. */ + /* Expand the helo_data string to find the host name to use. */ + + if (tf->helo_data != NULL) + { + uschar *s = expand_string(tf->helo_data); + if (s == NULL) + log_write(0, LOG_MAIN|LOG_PANIC, "<%s>: failed to expand transport's " + "helo_data value for callout: %s", addr->address, + expand_string_message); + else active_hostname = s; + } + + deliver_host = deliver_host_address = NULL; + deliver_domain = save_deliver_domain; + + /* Wait for initial response, and send HELO. The smtp_write_command() + function leaves its command in big_buffer. This is used in error responses. + Initialize it in case the connection is rejected. */ Ustrcpy(big_buffer, "initial connection"); done = smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout) && - smtp_write_command(&outblock, FALSE, "%s %s\r\n", helo, - smtp_active_hostname) >= 0 && + active_hostname) >= 0 && smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), - '2', callout) && + '2', callout); + + /* Failure to accept HELO is cached; this blocks the whole domain for all + senders. I/O errors and defer responses are not cached. */ + + if (!done) + { + *failure_ptr = US"mail"; /* At or before MAIL */ + if (errno == 0 && responsebuffer[0] == '5') + { + setflag(addr, af_verify_nsfail); + new_domain_record.result = ccache_reject; + } + } + + /* Send the MAIL command */ + else done = smtp_write_command(&outblock, FALSE, "MAIL FROM:<%s>\r\n", from_address) >= 0 && smtp_read_response(&inblock, responsebuffer, sizeof(responsebuffer), '2', callout); - /* If the host gave an initial error, or does not accept HELO or MAIL - FROM:<>, arrange to cache this information, but don't record anything for an - I/O error or a defer. Do not cache rejections when a non-empty sender has - been used, because that blocks the whole domain for all senders. */ + /* If the host does not accept MAIL FROM:<>, arrange to cache this + information, but again, don't record anything for an I/O error or a defer. Do + not cache rejections of MAIL when a non-empty sender has been used, because + that blocks the whole domain for all senders. */ if (!done) { - *failure_ptr = US"mail"; + *failure_ptr = US"mail"; /* At or before MAIL */ if (errno == 0 && responsebuffer[0] == '5') { setflag(addr, af_verify_nsfail); - if (from_address[0] == 0) new_domain_record.result = ccache_reject; + if (from_address[0] == 0) + new_domain_record.result = ccache_reject_mfnull; } } /* Otherwise, proceed to check a "random" address (if required), then the given address, and the postmaster address (if required). Between each check, issue RSET, because some servers accept only one recipient after MAIL - FROM:<>. */ + FROM:<>. + + Before doing this, set the result in the domain cache record to "accept", + unless its previous value was ccache_reject_mfnull. In that case, the domain + rejects MAIL FROM:<> and we want to continue to remember that. When that is + the case, we have got here only in the case of a recipient verification with + a non-null sender. */ else { - new_domain_record.result = 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 */ @@ -685,7 +744,7 @@ 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 or ccache_reject. */ +Otherwise the value is ccache_accept, ccache_reject, or ccache_reject_mfnull. */ if (!callout_no_cache && new_domain_record.result != ccache_unknown) { @@ -793,6 +852,8 @@ if (addr != vaddr) vaddr->user_message = addr->user_message; vaddr->basic_errno = addr->basic_errno; vaddr->more_errno = addr->more_errno; + vaddr->p.address_data = addr->p.address_data; + copyflag(vaddr, addr, af_pass_message); } return yield; } @@ -800,6 +861,42 @@ return yield; +/************************************************** +* printf that automatically handles TLS if needed * +***************************************************/ + +/* This function is used by verify_address() as a substitute for all fprintf() +calls; a direct fprintf() will not produce output in a TLS SMTP session, such +as a response to an EXPN command. smtp_in.c makes smtp_printf available but +that assumes that we always use the smtp_out FILE* when not using TLS or the +ssl buffer when we are. Instead we take a FILE* parameter and check to see if +that is smtp_out; if so, smtp_printf() with TLS support, otherwise regular +fprintf(). + +Arguments: + f the candidate FILE* to write to + format format string + ... optional arguments + +Returns: + nothing +*/ + +static void PRINTF_FUNCTION(2,3) +respond_printf(FILE *f, const char *format, ...) +{ +va_list ap; + +va_start(ap, format); +if (smtp_out && (f == smtp_out)) + smtp_vprintf(format, ap); +else + vfprintf(f, format, ap); +va_end(ap); +} + + + /************************************************* * Verify an email address * *************************************************/ @@ -899,8 +996,8 @@ if (parse_find_at(address) == NULL) if ((options & vopt_qualify) == 0) { if (f != NULL) - fprintf(f, "%sA domain is required for \"%s\"%s\n", ko_prefix, address, - cr); + respond_printf(f, "%sA domain is required for \"%s\"%s\n", + ko_prefix, address, cr); *failure_ptr = US"qualify"; return FAIL; } @@ -1040,10 +1137,21 @@ while (addr_new != NULL) { host_item *host_list = addr->host_list; - /* Default, if no remote transport, to NULL for the interface (=> any), - "smtp" for the port, and "smtp" for the protocol. */ - - transport_feedback tf = { NULL, US"smtp", US"smtp", NULL, FALSE, FALSE }; + /* Make up some data for use in the case where there is no remote + transport. */ + + transport_feedback tf = { + NULL, /* interface (=> any) */ + US"smtp", /* port */ + US"smtp", /* protocol */ + NULL, /* hosts */ + US"$smtp_active_hostname", /* helo_data */ + FALSE, /* hosts_override */ + FALSE, /* hosts_randomize */ + FALSE, /* gethostbyname */ + TRUE, /* qualify_single */ + FALSE /* search_parents */ + }; /* If verification yielded a remote transport, we want to use that transport's options, so as to mimic what would happen if we were really @@ -1051,7 +1159,7 @@ while (addr_new != NULL) if (addr->transport != NULL && !addr->transport->info->local) { - (void)(addr->transport->setup)(addr->transport, addr, &tf, NULL); + (void)(addr->transport->setup)(addr->transport, 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 @@ -1079,6 +1187,7 @@ while (addr_new != NULL) } else { + int flags; uschar *canonical_name; host_item *host, *nexthost; host_build_hostlist(&host_list, s, tf.hosts_randomize); @@ -1089,20 +1198,19 @@ while (addr_new != NULL) additional host items being inserted into the chain. Hence we must save the next host first. */ + flags = HOST_FIND_BY_A; + if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE; + if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS; + for (host = host_list; host != NULL; host = nexthost) { nexthost = host->next; if (tf.gethostbyname || string_is_ip_address(host->name, NULL) != 0) - (void)host_find_byname(host, NULL, &canonical_name, TRUE); + (void)host_find_byname(host, NULL, flags, &canonical_name, TRUE); else - { - int flags = HOST_FIND_BY_A; - if (tf.qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE; - if (tf.search_parents) flags |= HOST_FIND_SEARCH_PARENTS; (void)host_find_bydns(host, NULL, flags, NULL, NULL, NULL, &canonical_name, NULL); - } } } } @@ -1151,16 +1259,27 @@ while (addr_new != NULL) allok = FALSE; if (f != NULL) { - fprintf(f, "%s%s %s", ko_prefix, address, + address_item *p = addr->parent; + + respond_printf(f, "%s%s %s", ko_prefix, + full_info? addr->address : address, address_test_mode? "is undeliverable" : "failed to verify"); if (!expn && admin_user) { if (addr->basic_errno > 0) - fprintf(f, ": %s", strerror(addr->basic_errno)); + respond_printf(f, ": %s", strerror(addr->basic_errno)); if (addr->message != NULL) - fprintf(f, ":\n %s", addr->message); + respond_printf(f, ": %s", addr->message); + } + + /* Show parents iff doing full info */ + + if (full_info) while (p != NULL) + { + respond_printf(f, "%s\n <-- %s", cr, p->address); + p = p->parent; } - fprintf(f, "%s\n", cr); + respond_printf(f, "%s\n", cr); } if (!full_info) return copy_error(vaddr, addr, FAIL); @@ -1174,25 +1293,34 @@ while (addr_new != NULL) allok = FALSE; if (f != NULL) { - fprintf(f, "%s%s cannot be resolved at this time", ko_prefix, address); + address_item *p = addr->parent; + respond_printf(f, "%s%s cannot be resolved at this time", ko_prefix, + full_info? addr->address : address); if (!expn && admin_user) { if (addr->basic_errno > 0) - fprintf(f, ":\n %s", strerror(addr->basic_errno)); + respond_printf(f, ": %s", strerror(addr->basic_errno)); if (addr->message != NULL) - fprintf(f, ":\n %s", addr->message); + respond_printf(f, ": %s", addr->message); else if (addr->basic_errno <= 0) - fprintf(f, ":\n unknown error"); + respond_printf(f, ": unknown error"); } - fprintf(f, "%s\n", cr); + /* Show parents iff doing full info */ + + if (full_info) while (p != NULL) + { + respond_printf(f, "%s\n <-- %s", cr, p->address); + p = p->parent; + } + respond_printf(f, "%s\n", cr); } if (!full_info) return copy_error(vaddr, addr, DEFER); else if (yield == OK) yield = DEFER; } /* If we are handling EXPN, we do not want to continue to route beyond - the top level. */ + the top level (whose address is in "address"). */ else if (expn) { @@ -1200,16 +1328,16 @@ while (addr_new != NULL) if (addr_new == NULL) { if (addr_local == NULL && addr_remote == NULL) - fprintf(f, "250 mail to <%s> is discarded\r\n", address); + respond_printf(f, "250 mail to <%s> is discarded\r\n", address); else - fprintf(f, "250 <%s>\r\n", address); + respond_printf(f, "250 <%s>\r\n", address); } else while (addr_new != NULL) { address_item *addr2 = addr_new; addr_new = addr2->next; if (addr_new == NULL) ok_prefix = US"250 "; - fprintf(f, "%s<%s>\r\n", ok_prefix, addr2->address); + respond_printf(f, "%s<%s>\r\n", ok_prefix, addr2->address); } return OK; } @@ -1261,9 +1389,12 @@ or autoreplies, and there were no errors or deferments, the message is to be discarded, usually because of the use of :blackhole: in an alias file. */ if (allok && addr_local == NULL && addr_remote == NULL) + { fprintf(f, "mail to %s is discarded\n", address); + return yield; + } -else for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++) +for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++) { while (addr_list != NULL) { @@ -1276,6 +1407,19 @@ else for (addr_list = addr_local, i = 0; i < 2; addr_list = addr_remote, i++) if(addr->p.srs_sender) fprintf(f, " [srs = %s]", addr->p.srs_sender); #endif + + /* If the address is a duplicate, show something about it. */ + + if (!testflag(addr, af_pfr)) + { + tree_node *tnode; + if ((tnode = tree_search(tree_duplicates, addr->unique)) != NULL) + fprintf(f, " [duplicate, would not be delivered]"); + else tree_add_duplicate(addr->unique, addr); + } + + /* Now show its parents */ + while (p != NULL) { fprintf(f, "\n <-- %s", p->address); @@ -1359,8 +1503,9 @@ verify_check_headers(uschar **msgptr) { header_line *h; uschar *colon, *s; +int yield = OK; -for (h = header_list; h != NULL; h = h->next) +for (h = header_list; h != NULL && yield == OK; h = h->next) { if (h->type != htype_from && h->type != htype_reply_to && @@ -1374,9 +1519,10 @@ for (h = header_list; h != NULL; h = h->next) s = colon + 1; while (isspace(*s)) s++; - parse_allow_group = TRUE; /* Allow group syntax */ + /* Loop for multiple addresses in the header, enabling group syntax. Note + that we have to reset this after the header has been scanned. */ - /* Loop for multiple addresses in the header */ + parse_allow_group = TRUE; while (*s != 0) { @@ -1386,7 +1532,7 @@ for (h = header_list; h != NULL; h = h->next) int start, end, domain; /* Temporarily terminate the string at this point, and extract the - operative address within. */ + operative address within, allowing group syntax. */ *ss = 0; recipient = parse_extract_address(s,&errmess,&start,&end,&domain,FALSE); @@ -1415,14 +1561,16 @@ for (h = header_list; h != NULL; h = h->next) { uschar *verb = US"is"; uschar *t = ss; + uschar *tt = colon; int len; /* Arrange not to include any white space at the end in the - error message. */ + error message or the header name. */ while (t > s && isspace(t[-1])) t--; + while (tt > h->text && isspace(tt[-1])) tt--; - /* Add the address which failed to the error message, since in a + /* Add the address that failed to the error message, since in a header with very many addresses it is sometimes hard to spot which one is at fault. However, limit the amount of address to quote - cases have been seen where, for example, a missing double @@ -1437,10 +1585,11 @@ for (h = header_list; h != NULL; h = h->next) } *msgptr = string_printing( - string_sprintf("%s: failing address in \"%.*s\" header %s: %.*s", - errmess, colon - h->text, h->text, verb, len, s)); + string_sprintf("%s: failing address in \"%.*s:\" header %s: %.*s", + errmess, tt - h->text, h->text, verb, len, s)); - return FAIL; + yield = FAIL; + break; /* Out of address loop */ } /* Advance to the next address */ @@ -1448,9 +1597,12 @@ for (h = header_list; h != NULL; h = h->next) s = ss + (terminator? 1:0); while (isspace(*s)) s++; } /* Next address */ - } /* Next header */ -return OK; + parse_allow_group = FALSE; + parse_found_group = FALSE; + } /* Next header unless yield has been set FALSE */ + +return yield; } @@ -1493,9 +1645,10 @@ for (i = 0; i < recipients_count; i++) s = colon + 1; while (isspace(*s)) s++; - parse_allow_group = TRUE; /* Allow group syntax */ + /* Loop for multiple addresses in the header, enabling group syntax. Note + that we have to reset this after the header has been scanned. */ - /* Loop for multiple addresses in the header */ + parse_allow_group = TRUE; while (*s != 0) { @@ -1505,7 +1658,7 @@ for (i = 0; i < recipients_count; i++) int start, end, domain; /* Temporarily terminate the string at this point, and extract the - operative address within. */ + operative address within, allowing group syntax. */ *ss = 0; recipient = parse_extract_address(s,&errmess,&start,&end,&domain,FALSE); @@ -1529,6 +1682,9 @@ for (i = 0; i < recipients_count; i++) s = ss + (terminator? 1:0); while (isspace(*s)) s++; } /* Next address */ + + parse_allow_group = FALSE; + parse_found_group = FALSE; } /* Next header (if found is false) */ if (!found) return FAIL; @@ -1611,13 +1767,14 @@ verify_check_header_address(uschar **user_msgptr, uschar **log_msgptr, uschar *pm_mailfrom, int options, int *verrno) { static int header_types[] = { htype_sender, htype_reply_to, htype_from }; +BOOL done = FALSE; int yield = FAIL; int i; -for (i = 0; i < 3; i++) +for (i = 0; i < 3 && !done; i++) { header_line *h; - for (h = header_list; h != NULL; h = h->next) + for (h = header_list; h != NULL && !done; h = h->next) { int terminator, new_ok; uschar *s, *ss, *endname; @@ -1625,6 +1782,11 @@ for (i = 0; i < 3; i++) if (h->type != header_types[i]) continue; s = endname = Ustrchr(h->text, ':') + 1; + /* Scan the addresses in the header, enabling group syntax. Note that we + have to reset this after the header has been scanned. */ + + parse_allow_group = TRUE; + while (*s != 0) { address_item *vaddr; @@ -1667,11 +1829,21 @@ for (i = 0; i < 3; i++) else { int start, end, domain; - uschar *address = parse_extract_address(s, log_msgptr, &start, - &end, &domain, FALSE); + uschar *address = parse_extract_address(s, log_msgptr, &start, &end, + &domain, FALSE); *ss = terminator; + /* 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) + { + *log_msgptr = NULL; + s = ss; + continue; + } + /* If verification failed because of a syntax error, fail this function, and ensure that the failing address gets added to the error message. */ @@ -1679,14 +1851,13 @@ for (i = 0; i < 3; i++) if (address == NULL) { new_ok = FAIL; - if (*log_msgptr != NULL) - { - while (ss > s && isspace(ss[-1])) ss--; - *log_msgptr = string_sprintf("syntax error in '%.*s' header when " - "scanning for sender: %s in \"%.*s\"", - endname - h->text, h->text, *log_msgptr, ss - s, s); - return FAIL; - } + while (ss > s && isspace(ss[-1])) ss--; + *log_msgptr = string_sprintf("syntax error in '%.*s' header when " + "scanning for sender: %s in \"%.*s\"", + endname - h->text, h->text, *log_msgptr, ss - s, s); + yield = FAIL; + done = TRUE; + break; } /* Else go ahead with the sender verification. But it isn't *the* @@ -1720,15 +1891,24 @@ for (i = 0; i < 3; i++) /* Success or defer */ - if (new_ok == OK) return OK; + if (new_ok == OK) + { + yield = OK; + done = TRUE; + break; + } + if (new_ok == DEFER) yield = DEFER; /* Move on to any more addresses in the header */ s = ss; - } - } - } + } /* Next address */ + + parse_allow_group = FALSE; + parse_found_group = FALSE; + } /* Next header, unless done */ + } /* Next header type unless done */ if (yield == FAIL && *log_msgptr == NULL) *log_msgptr = US"there is no valid sender in any header line"; @@ -2056,7 +2236,8 @@ if (iplookup) 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. */ + dot separators instead of colons, except when the lookup type is "iplsearch". + */ if (mac_islookup(search_type, lookup_absfilequery)) { @@ -2071,11 +2252,13 @@ if (iplookup) filename = NULL; key = semicolon + 1; } - else + else /* Single-key style */ { + int sep = (Ustrcmp(lookup_list[search_type]->name, "iplsearch") == 0)? + ':' : '.'; insize = host_aton(cb->host_address, incoming); host_mask(insize, incoming, mlen); - (void)host_nmtoa(insize, incoming, mlen, buffer, '.'); + (void)host_nmtoa(insize, incoming, mlen, buffer, sep); key = buffer; filename = semicolon + 1; } @@ -2122,7 +2305,8 @@ if (*t == 0) h.name = ss; h.address = NULL; h.mx = MX_NONE; - rc = host_find_byname(&h, NULL, NULL, FALSE); + + rc = host_find_byname(&h, NULL, HOST_FIND_QUALIFY_SINGLE, NULL, FALSE); if (rc == HOST_FOUND || rc == HOST_FOUND_LOCAL) { host_item *hh; @@ -2327,16 +2511,18 @@ return verify_check_this_host(listptr, sender_host_cache, NULL, /************************************************* -* Invert an IP address for a DNS black list * +* Invert an IP address * *************************************************/ -/* +/* Originally just used for DNS xBL lists, now also used for the +reverse_ip expansion operator. + Arguments: buffer where to put the answer address the address to invert */ -static void +void invert_address(uschar *buffer, uschar *address) { int bin[4]; @@ -2382,6 +2568,12 @@ else } } #endif + +/* Remove trailing period -- this is needed so that both arbitrary +dnsbl keydomains and inverted addresses may be combined with the +same format string, "%s.%s" */ + +*(--bptr) = 0; } @@ -2390,15 +2582,26 @@ else * Perform a single dnsbl lookup * *************************************************/ -/* This function is called from verify_check_dnsbl() below. +/* This function is called from verify_check_dnsbl() below. It is also called +recursively from within itself when domain and domain_txt are different +pointers, in order to get the TXT record from the alternate domain. Arguments: - domain the outer dnsbl domain (for debug message) + domain the outer dnsbl domain + domain_txt alternate domain to lookup TXT record on success; when the + same domain is to be used, domain_txt == domain (that is, + the pointers must be identical, not just the text) keydomain the current keydomain (for debug message) - query the domain to be looked up - iplist the list of matching IP addresses + prepend subdomain to lookup (like keydomain, but + reversed if IP address) + iplist the list of matching IP addresses, or NULL for "any" bitmask true if bitmask matching is wanted - invert_result true if result to be inverted + match_type condition for 'succeed' result + 0 => Any RR in iplist (=) + 1 => No RR in iplist (!=) + 2 => All RRs in iplist (==) + 3 => Some RRs not in iplist (!==) + the two bits are defined as MT_NOT and MT_ALL defer_return what to return for a defer Returns: OK if lookup succeeded @@ -2406,14 +2609,25 @@ Returns: OK if lookup succeeded */ static int -one_check_dnsbl(uschar *domain, uschar *keydomain, uschar *query, - uschar *iplist, BOOL bitmask, BOOL invert_result, int defer_return) +one_check_dnsbl(uschar *domain, uschar *domain_txt, uschar *keydomain, + uschar *prepend, uschar *iplist, BOOL bitmask, int match_type, + int defer_return) { dns_answer dnsa; dns_scan dnss; tree_node *t; dnsbl_cache_block *cb; int old_pool = store_pool; +uschar query[256]; /* DNS domain max length */ + +/* Construct the specific query domainname */ + +if (!string_format(query, sizeof(query), "%s.%s", prepend, domain)) + { + log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long " + "(ignored): %s...", query); + return FAIL; + } /* Look for this query in the cache. */ @@ -2515,21 +2729,25 @@ if (cb->rc == DNS_SUCCEED) if (iplist != NULL) { - int ipsep = ','; - uschar ip[46]; - uschar *ptr = iplist; - - while (string_nextinlist(&ptr, &ipsep, ip, sizeof(ip)) != NULL) + for (da = cb->rhs; da != NULL; da = da->next) { + int ipsep = ','; + uschar ip[46]; + uschar *ptr = iplist; + uschar *res; + /* Handle exact matching */ + if (!bitmask) { - for (da = cb->rhs; da != NULL; da = da->next) + while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))) != NULL) { if (Ustrcmp(CS da->address, ip) == 0) break; } } + /* Handle bitmask matching */ + else { int address[4]; @@ -2542,44 +2760,77 @@ if (cb->rc == DNS_SUCCEED) ignore IPv6 addresses. The default mask is 0, which always matches. We change this only for IPv4 addresses in the list. */ - if (host_aton(ip, address) == 1) mask = address[0]; + if (host_aton(da->address, address) == 1) mask = address[0]; /* Scan the returned addresses, skipping any that are IPv6 */ - for (da = cb->rhs; da != NULL; da = da->next) + while ((res = string_nextinlist(&ptr, &ipsep, ip, sizeof(ip))) != NULL) { - if (host_aton(da->address, address) != 1) continue; - if ((address[0] & mask) == mask) break; + if (host_aton(ip, address) != 1) continue; + if ((address[0] & mask) == address[0]) break; } } - /* Break out if a match has been found */ + /* If either + + (a) An IP address in an any ('=') list matched, or + (b) No IP address in an all ('==') list matched + + then we're done searching. */ - if (da != NULL) break; + if (((match_type & MT_ALL) != 0) == (res == NULL)) break; } - /* If either + /* If da == NULL, either - (a) No IP address in a positive list matched, or - (b) An IP address in a negative list did match + (a) No IP address in an any ('=') list matched, or + (b) An IP address in an all ('==') list didn't match - then behave as if the DNSBL lookup had not succeeded, i.e. the host is - not on the list. */ + so behave as if the DNSBL lookup had not succeeded, i.e. the host is not on + the list. */ - if (invert_result != (da == NULL)) + if ((match_type == MT_NOT || match_type == MT_ALL) != (da == NULL)) { HDEBUG(D_dnsbl) { + uschar *res = NULL; + switch(match_type) + { + case 0: + res = US"was no match"; + break; + case MT_NOT: + res = US"was an exclude match"; + break; + case MT_ALL: + res = US"was an IP address that did not match"; + break; + case MT_NOT|MT_ALL: + res = US"were no IP addresses that did not match"; + break; + } debug_printf("=> but we are not accepting this block class because\n"); - debug_printf("=> there was %s match for %c%s\n", - invert_result? "an exclude":"no", bitmask? '&' : '=', iplist); + debug_printf("=> there %s for %s%c%s\n", + res, + ((match_type & MT_ALL) == 0)? "" : "=", + bitmask? '&' : '=', iplist); } return FAIL; } } - /* Either there was no IP list, or the record matched. Look up a TXT record - if it hasn't previously been done. */ + /* Either there was no IP list, or the record matched, implying that the + domain is on the list. We now want to find a corresponding TXT record. If an + alternate domain is specified for the TXT record, call this function + recursively to look that up; this has the side effect of re-checking that + there is indeed an A record at the alternate domain. */ + + if (domain_txt != domain) + return one_check_dnsbl(domain_txt, domain_txt, keydomain, prepend, NULL, + FALSE, match_type, defer_return); + + /* If there is no alternate domain, look up a TXT record in the main domain + if it has not previously been cached. */ if (!cb->text_set) { @@ -2650,7 +2901,7 @@ given, comma-separated, for example: x.y.z=127.0.0.1,127.0.0.2. If no key is given, what is looked up in the domain is the inverted IP address of the current client host. If a key is given, it is used to construct the -domain for the lookup. For example, +domain for the lookup. For example: dsn.rfc-ignorant.org/$sender_address_domain @@ -2659,6 +2910,17 @@ then we check for a TXT record for an error message, and if found, save its value in $dnslist_text. We also cache everything in a tree, to optimize multiple lookups. +The TXT record is normally looked up in the same domain as the A record, but +when many lists are combined in a single DNS domain, this will not be a very +specific message. It is possible to specify a different domain for looking up +TXT records; this is given before the main domain, comma-separated. For +example: + + dnslists = http.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.2 : \ + socks.dnsbl.sorbs.net,dnsbl.sorbs.net=127.0.0.3 + +The caching ensures that only one lookup in dnsbl.sorbs.net is done. + Note: an address for testing RBL is 192.203.178.39 Note: an address for testing DUL is 192.203.178.4 Note: a domain for testing RFCI is example.tld.dsn.rfc-ignorant.org @@ -2678,12 +2940,10 @@ verify_check_dnsbl(uschar **listptr) { int sep = 0; int defer_return = FAIL; -BOOL invert_result = FALSE; uschar *list = *listptr; uschar *domain; uschar *s; uschar buffer[1024]; -uschar query[256]; /* DNS domain max length */ uschar revadd[128]; /* Long enough for IPv6 address */ /* Indicate that the inverted IP address is not yet set up */ @@ -2699,8 +2959,10 @@ dns_init(FALSE, FALSE); while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL) { int rc; - BOOL frc; BOOL bitmask = FALSE; + int match_type = 0; + uschar *domain_txt; + uschar *comma; uschar *iplist; uschar *key; @@ -2725,8 +2987,8 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL if (key != NULL) *key++ = 0; /* See if there's a list of addresses supplied after the domain name. This is - introduced by an = or a & character; if preceded by ! we invert the result. - */ + introduced by an = or a & character; if preceded by = we require all matches + and if preceded by ! we invert the result. */ iplist = Ustrchr(domain, '='); if (iplist == NULL) @@ -2735,14 +2997,35 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL iplist = Ustrchr(domain, '&'); } - if (iplist != NULL) + if (iplist != NULL) /* Found either = or & */ { - if (iplist > domain && iplist[-1] == '!') + if (iplist > domain && iplist[-1] == '!') /* Handle preceding ! */ { - invert_result = TRUE; + match_type |= MT_NOT; iplist[-1] = 0; } - *iplist++ = 0; + + *iplist++ = 0; /* Terminate domain, move on */ + + /* If we found = (bitmask == FALSE), check for == or =& */ + + if (!bitmask && (*iplist == '=' || *iplist == '&')) + { + bitmask = *iplist++ == '&'; + match_type |= MT_ALL; + } + } + + /* If there is a comma in the domain, it indicates that a second domain for + looking up TXT records is provided, before the main domain. Otherwise we must + set domain_txt == domain. */ + + domain_txt = domain; + comma = Ustrchr(domain, ','); + if (comma != NULL) + { + *comma++ = 0; + domain = comma; } /* Check that what we have left is a sensible domain name. There is no reason @@ -2753,7 +3036,7 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL for (s = domain; *s != 0; s++) { - if (!isalnum(*s) && *s != '-' && *s != '.') + if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_') { log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains " "strange characters - is this right?", domain); @@ -2761,6 +3044,18 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL } } + /* Check the alternate domain if present */ + + if (domain_txt != domain) for (s = domain_txt; *s != 0; s++) + { + if (!isalnum(*s) && *s != '-' && *s != '.' && *s != '_') + { + log_write(0, LOG_MAIN, "dnslists domain \"%s\" contains " + "strange characters - is this right?", domain_txt); + break; + } + } + /* If there is no key string, construct the query by adding the domain name onto the inverted host address, and perform a single DNS lookup. */ @@ -2768,25 +3063,15 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL { if (sender_host_address == NULL) return FAIL; /* can never match */ if (revadd[0] == 0) invert_address(revadd, sender_host_address); - frc = string_format(query, sizeof(query), "%s%s", revadd, domain); - - if (!frc) - { - log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long " - "(ignored): %s...", query); - continue; - } - - rc = one_check_dnsbl(domain, sender_host_address, query, iplist, bitmask, - invert_result, defer_return); - + rc = one_check_dnsbl(domain, domain_txt, sender_host_address, revadd, + iplist, bitmask, match_type, defer_return); if (rc == OK) { - dnslist_domain = string_copy(domain); + dnslist_domain = string_copy(domain_txt); + dnslist_matched = string_copy(sender_host_address); HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n", - sender_host_address, domain); + sender_host_address, dnslist_domain); } - if (rc != FAIL) return rc; /* OK or DEFER */ } @@ -2799,36 +3084,28 @@ while ((domain = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL BOOL defer = FALSE; uschar *keydomain; uschar keybuffer[256]; + uschar keyrevadd[128]; while ((keydomain = string_nextinlist(&key, &keysep, keybuffer, sizeof(keybuffer))) != NULL) { + uschar *prepend = keydomain; + if (string_is_ip_address(keydomain, NULL) != 0) { - uschar keyrevadd[128]; invert_address(keyrevadd, keydomain); - frc = string_format(query, sizeof(query), "%s%s", keyrevadd, domain); - } - else - { - frc = string_format(query, sizeof(query), "%s.%s", keydomain, domain); - } - - if (!frc) - { - log_write(0, LOG_MAIN|LOG_PANIC, "dnslist query is too long " - "(ignored): %s...", query); - continue; + prepend = keyrevadd; } - rc = one_check_dnsbl(domain, keydomain, query, iplist, bitmask, - invert_result, defer_return); + rc = one_check_dnsbl(domain, domain_txt, keydomain, prepend, iplist, + bitmask, match_type, defer_return); if (rc == OK) { - dnslist_domain = string_copy(domain); + dnslist_domain = string_copy(domain_txt); + dnslist_matched = string_copy(keydomain); HDEBUG(D_dnsbl) debug_printf("=> that means %s is listed at %s\n", - keydomain, domain); + keydomain, dnslist_domain); return OK; }