X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/2548ba04544aa8a42cdc5c69cd48171898949b54..d7d7b7b91dd75cec636fc144da7e27eed860f971:/src/src/host.c diff --git a/src/src/host.c b/src/src/host.c index d8f560788..317d257bb 100644 --- a/src/src/host.c +++ b/src/src/host.c @@ -1,10 +1,10 @@ -/* $Cambridge: exim/src/src/host.c,v 1.11 2005/08/02 09:01:44 ph10 Exp $ */ +/* $Cambridge: exim/src/src/host.c,v 1.20 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 for finding hosts, either by gethostbyname(), gethostbyaddr(), or @@ -102,6 +102,13 @@ number of multihomed host IP addresses into the order, so as to get repeatability. This doesn't have to be efficient. But don't interchange IPv4 and IPv6 addresses! +NOTE: +This sorting is not necessary for the new test harness, because it +doesn't call the real DNS resolver, and its output is repeatable. However, +until the old test harness is discarded, we need to retain this capability. +The new harness is being developed towards the end of 2005. It will be some +time before it can do everything that the old one can do. + Arguments: host -> the first host item last -> the last host item @@ -135,6 +142,186 @@ while (!done) +/************************************************* +* Replace gethostbyname() when testing * +*************************************************/ + +/* This function is called instead of gethostbyname(), gethostbyname2(), or +getipnodebyname() when running in the test harness. It recognizes the name +"manyhome.test.ex" and generates a humungous number of IP addresses. It also +recognizes an unqualified "localhost" and forces it to the appropriate loopback +address. IP addresses are treated as literals. For other names, it uses the DNS +to find the host name. In the new test harness, this means it will access only +the fake DNS resolver. In the old harness it will call the real resolver and +access the test zone. + +Arguments: + name the host name or a textual IP address + af AF_INET or AF_INET6 + error_num where to put an error code: + HOST_NOT_FOUND/TRY_AGAIN/NO_RECOVERY/NO_DATA + +Returns: a hostent structure or NULL for an error +*/ + +static struct hostent * +host_fake_gethostbyname(uschar *name, int af, int *error_num) +{ +#if HAVE_IPV6 +int alen = (af == AF_INET)? sizeof(struct in_addr):sizeof(struct in6_addr); +#else +int alen = sizeof(struct in_addr); +#endif + +int ipa; +uschar *lname = name; +uschar *adds; +uschar **alist; +struct hostent *yield; +dns_answer dnsa; +dns_scan dnss; +dns_record *rr; + +DEBUG(D_host_lookup) + debug_printf("using host_fake_gethostbyname for %s (%s)\n", name, + (af == AF_INET)? "IPv4" : "IPv6"); + +/* Handle the name that needs a vast number of IP addresses */ + +if (Ustrcmp(name, "manyhome.test.ex") == 0 && af == AF_INET) + { + int i, j; + yield = store_get(sizeof(struct hostent)); + alist = store_get(2049 * sizeof(char *)); + adds = store_get(2048 * alen); + yield->h_name = CS name; + yield->h_aliases = NULL; + yield->h_addrtype = af; + yield->h_length = alen; + yield->h_addr_list = CSS alist; + for (i = 104; i <= 111; i++) + { + for (j = 0; j <= 255; j++) + { + *alist++ = adds; + *adds++ = 10; + *adds++ = 250; + *adds++ = i; + *adds++ = j; + } + } + *alist = NULL; + return yield; + } + +/* Handle unqualified "localhost" */ + +if (Ustrcmp(name, "localhost") == 0) + lname = (af == AF_INET)? US"127.0.0.1" : US"::1"; + +/* Handle a literal IP address */ + +ipa = string_is_ip_address(lname, NULL); +if (ipa != 0) + { + if ((ipa == 4 && af == AF_INET) || + (ipa == 6 && af == AF_INET6)) + { + int i, n; + int x[4]; + yield = store_get(sizeof(struct hostent)); + alist = store_get(2 * sizeof(char *)); + adds = store_get(alen); + yield->h_name = CS name; + yield->h_aliases = NULL; + yield->h_addrtype = af; + yield->h_length = alen; + yield->h_addr_list = CSS alist; + *alist++ = adds; + n = host_aton(lname, x); + for (i = 0; i < n; i++) + { + int y = x[i]; + *adds++ = (y >> 24) & 255; + *adds++ = (y >> 16) & 255; + *adds++ = (y >> 8) & 255; + *adds++ = y & 255; + } + *alist = NULL; + } + + /* Wrong kind of literal address */ + + else + { + *error_num = HOST_NOT_FOUND; + return NULL; + } + } + +/* Handle a host name */ + +else + { + int type = (af == AF_INET)? T_A:T_AAAA; + int rc = dns_lookup(&dnsa, lname, type, NULL); + int count = 0; + + switch(rc) + { + case DNS_SUCCEED: break; + case DNS_NOMATCH: *error_num = HOST_NOT_FOUND; return NULL; + case DNS_NODATA: *error_num = NO_DATA; return NULL; + case DNS_AGAIN: *error_num = TRY_AGAIN; return NULL; + default: + case DNS_FAIL: *error_num = NO_RECOVERY; return NULL; + } + + for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); + rr != NULL; + rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) + { + if (rr->type == type) count++; + } + + yield = store_get(sizeof(struct hostent)); + alist = store_get((count + 1) * sizeof(char **)); + adds = store_get(count *alen); + + yield->h_name = CS name; + yield->h_aliases = NULL; + yield->h_addrtype = af; + yield->h_length = alen; + yield->h_addr_list = CSS alist; + + for (rr = dns_next_rr(&dnsa, &dnss, RESET_ANSWERS); + rr != NULL; + rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT)) + { + int i, n; + int x[4]; + dns_address *da; + if (rr->type != type) continue; + da = dns_address_from_rr(&dnsa, rr); + *alist++ = adds; + n = host_aton(da->address, x); + for (i = 0; i < n; i++) + { + int y = x[i]; + *adds++ = (y >> 24) & 255; + *adds++ = (y >> 16) & 255; + *adds++ = (y >> 8) & 255; + *adds++ = y & 255; + } + } + *alist = NULL; + } + +return yield; +} + + + /************************************************* * Build chain of host items from list * *************************************************/ @@ -237,7 +424,7 @@ Returns: 0 if there is no port, else the port number. If there's a syntax */ int -host_extract_port(uschar *address) +host_address_extract_port(uschar *address) { int port = 0; uschar *endptr; @@ -281,6 +468,59 @@ return port; } +/************************************************* +* Get port from a host item's name * +*************************************************/ + +/* This function is called when finding the IP address for a host that is in a +list of hosts explicitly configured, such as in the manualroute router, or in a +fallback hosts list. We see if there is a port specification at the end of the +host name, and if so, remove it. A minimum length of 3 is required for the +original name; nothing shorter is recognized as having a port. + +We test for a name ending with a sequence of digits; if preceded by colon we +have a port if the character before the colon is ] and the name starts with [ +or if there are no other colons in the name (i.e. it's not an IPv6 address). + +Arguments: pointer to the host item +Returns: a port number or PORT_NONE +*/ + +int +host_item_get_port(host_item *h) +{ +uschar *p; +int port, x; +int len = Ustrlen(h->name); + +if (len < 3 || (p = h->name + len - 1, !isdigit(*p))) return PORT_NONE; + +/* Extract potential port number */ + +port = *p-- - '0'; +x = 10; + +while (p > h->name + 1 && isdigit(*p)) + { + port += (*p-- - '0') * x; + x *= 10; + } + +/* The smallest value of p at this point is h->name + 1. */ + +if (*p != ':') return PORT_NONE; + +if (p[-1] == ']' && h->name[0] == '[') + h->name = string_copyn(h->name + 1, p - h->name - 2); +else if (Ustrchr(h->name, ':') == p) + h->name = string_copyn(h->name, p - h->name); +else return PORT_NONE; + +DEBUG(D_route|D_host_lookup) debug_printf("host=%s port=%d\n", h->name, port); +return port; +} + + #ifndef STAND_ALONE /* Omit when standalone testing */ @@ -295,8 +535,9 @@ as follows: (a) No sender_host_name or sender_helo_name: "[ip address]" (b) Just sender_host_name: "host_name [ip address]" -(c) Just sender_helo_name: "(helo_name) [ip address]" -(d) The two are identical: "host_name [ip address]" +(c) Just sender_helo_name: "(helo_name) [ip address]" unless helo is IP + in which case: "[ip address}" +(d) The two are identical: "host_name [ip address]" includes helo = IP (e) The two are different: "host_name (helo_name) [ip address]" If log_incoming_port is set, the sending host's port number is added to the IP @@ -317,7 +558,9 @@ Returns: nothing void host_build_sender_fullhost(void) { +BOOL show_helo = TRUE; uschar *address; +int len; int old_pool = store_pool; if (sender_host_address == NULL) return; @@ -333,6 +576,43 @@ address = string_sprintf("[%s]:%d", sender_host_address, sender_host_port); if ((log_extra_selector & LX_incoming_port) == 0 || sender_host_port <= 0) *(Ustrrchr(address, ':')) = 0; +/* If there's no EHLO/HELO data, we can't show it. */ + +if (sender_helo_name == NULL) show_helo = FALSE; + +/* If HELO/EHLO was followed by an IP literal, it's messy because of two +features of IPv6. Firstly, there's the "IPv6:" prefix (Exim is liberal and +doesn't require this, for historical reasons). Secondly, IPv6 addresses may not +be given in canonical form, so we have to canonicize them before comparing. As +it happens, the code works for both IPv4 and IPv6. */ + +else if (sender_helo_name[0] == '[' && + sender_helo_name[(len=Ustrlen(sender_helo_name))-1] == ']') + { + int offset = 1; + uschar *helo_ip; + + if (strncmpic(sender_helo_name + 1, US"IPv6:", 5) == 0) offset += 5; + if (strncmpic(sender_helo_name + 1, US"IPv4:", 5) == 0) offset += 5; + + helo_ip = string_copyn(sender_helo_name + offset, len - offset - 1); + + if (string_is_ip_address(helo_ip, NULL) != 0) + { + int x[4], y[4]; + int sizex, sizey; + uschar ipx[48], ipy[48]; /* large enough for full IPv6 */ + + sizex = host_aton(helo_ip, x); + sizey = host_aton(sender_host_address, y); + + (void)host_nmtoa(sizex, x, -1, ipx, ':'); + (void)host_nmtoa(sizey, y, -1, ipy, ':'); + + if (strcmpic(ipx, ipy) == 0) show_helo = FALSE; + } + } + /* Host name is not verified */ if (sender_host_name == NULL) @@ -348,7 +628,7 @@ if (sender_host_name == NULL) sender_rcvhost = string_cat(NULL, &size, &ptr, address, adlen); - if (sender_ident != NULL || sender_helo_name != NULL || portptr != NULL) + if (sender_ident != NULL || show_helo || portptr != NULL) { int firstptr; sender_rcvhost = string_cat(sender_rcvhost, &size, &ptr, US" (", 2); @@ -358,7 +638,7 @@ if (sender_host_name == NULL) sender_rcvhost = string_append(sender_rcvhost, &size, &ptr, 2, US"port=", portptr + 1); - if (sender_helo_name != NULL) + if (show_helo) sender_rcvhost = string_append(sender_rcvhost, &size, &ptr, 2, (firstptr == ptr)? US"helo=" : US" helo=", sender_helo_name); @@ -377,24 +657,15 @@ if (sender_host_name == NULL) store_reset(sender_rcvhost + ptr + 1); } -/* Host name is known and verified. */ +/* Host name is known and verified. Unless we've already found that the HELO +data matches the IP address, compare it with the name. */ else { - int len; - if (sender_helo_name == NULL || - strcmpic(sender_host_name, sender_helo_name) == 0 || - (sender_helo_name[0] == '[' && - sender_helo_name[(len=Ustrlen(sender_helo_name))-1] == ']' && - strncmpic(sender_helo_name+1, sender_host_address, len - 2) == 0)) - { - sender_fullhost = string_sprintf("%s %s", sender_host_name, address); - sender_rcvhost = (sender_ident == NULL)? - string_sprintf("%s (%s)", sender_host_name, address) : - string_sprintf("%s (%s ident=%s)", sender_host_name, address, - sender_ident); - } - else + if (show_helo && strcmpic(sender_host_name, sender_helo_name) == 0) + show_helo = FALSE; + + if (show_helo) { sender_fullhost = string_sprintf("%s (%s) %s", sender_host_name, sender_helo_name, address); @@ -404,6 +675,14 @@ else string_sprintf("%s\n\t(%s helo=%s ident=%s)", sender_host_name, address, sender_helo_name, sender_ident); } + else + { + sender_fullhost = string_sprintf("%s %s", sender_host_name, address); + sender_rcvhost = (sender_ident == NULL)? + string_sprintf("%s (%s)", sender_host_name, address) : + string_sprintf("%s (%s ident=%s)", sender_host_name, address, + sender_ident); + } } store_pool = old_pool; @@ -495,11 +774,16 @@ ip_address_item *next; while ((s = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL) { - int port = host_extract_port(s); /* Leaves just the IP address */ - if (string_is_ip_address(s, NULL) == 0) + int ipv; + int port = host_address_extract_port(s); /* Leaves just the IP address */ + if ((ipv = string_is_ip_address(s, NULL)) == 0) log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Malformed IP address \"%s\" in %s", s, name); + /* Skip IPv6 addresses if IPv6 is disabled. */ + + if (disable_ipv6 && ipv == 6) continue; + /* This use of strcpy() is OK because we have checked that s is a valid IP address above. The field in the ip_address_item is large enough to hold an IPv6 address. */ @@ -1557,13 +1841,10 @@ for (hname = sender_host_name; hname != NULL; hname = *aliases++) if ((rc = host_find_byname(&h, NULL, NULL, FALSE)) == HOST_FOUND) { host_item *hh; - uschar *address_ipv4 = (Ustrncmp(sender_host_address, "::ffff:", 7) == 0)? - sender_host_address + 7 : sender_host_address; HDEBUG(D_host_lookup) debug_printf("checking addresses for %s\n", hname); for (hh = &h; hh != NULL; hh = hh->next) { - if ((Ustrcmp(hh->address, (Ustrchr(hh->address, ':') == NULL)? - address_ipv4 : sender_host_address)) == 0) + if (host_is_in_net(hh->address, sender_host_address, 0)) { HDEBUG(D_host_lookup) debug_printf(" %s OK\n", hh->address); ok = TRUE; @@ -1689,17 +1970,17 @@ if (running_in_test_harness) return HOST_FIND_AGAIN; } -/* In an IPv6 world, we need to scan for both kinds of address, so go round the -loop twice. Note that we have ensured that AF_INET6 is defined even in an IPv4 -world, which makes for slightly tidier code. However, if dns_ipv4_lookup -matches the domain, we also just do IPv4 lookups here (except when testing -standalone). */ +/* In an IPv6 world, unless IPv6 has been disabled, we need to scan for both +kinds of address, so go round the loop twice. Note that we have ensured that +AF_INET6 is defined even in an IPv4 world, which makes for slightly tidier +code. However, if dns_ipv4_lookup matches the domain, we also just do IPv4 +lookups here (except when testing standalone). */ #if HAVE_IPV6 #ifndef STAND_ALONE - if (dns_ipv4_lookup != NULL && + if (disable_ipv6 || (dns_ipv4_lookup != NULL && match_isinlist(host->name, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN, - TRUE, NULL) == OK) + TRUE, NULL) == OK)) { af = AF_INET; times = 1; } else #endif /* STAND_ALONE */ @@ -1730,16 +2011,27 @@ for (i = 1; i <= times; struct hostent *hostdata; #if HAVE_IPV6 + if (running_in_test_harness) + hostdata = host_fake_gethostbyname(host->name, af, &error_num); + else + { #if HAVE_GETIPNODEBYNAME hostdata = getipnodebyname(CS host->name, af, 0, &error_num); #else hostdata = gethostbyname2(CS host->name, af); error_num = h_errno; #endif - #else - hostdata = gethostbyname(CS host->name); - error_num = h_errno; - #endif + } + + #else /* not HAVE_IPV6 */ + if (running_in_test_harness) + hostdata = host_fake_gethostbyname(host->name, AF_INET, &error_num); + else + { + hostdata = gethostbyname(CS host->name); + error_num = h_errno; + } + #endif /* HAVE_IPV6 */ if (hostdata == NULL) { @@ -1962,19 +2254,18 @@ if (allow_ip && string_is_ip_address(host->name, NULL) != 0) return HOST_FOUND; } -/* On an IPv6 system, go round the loop up to three times, looking for A6 and -AAAA records the first two times. However, unless doing standalone testing, we -force an IPv4 lookup if the domain matches dns_ipv4_lookup is set. Since A6 -records look like being abandoned, support them only if explicitly configured -to do so. On an IPv4 system, go round the loop once only, looking only for A -records. */ +/* On an IPv6 system, unless IPv6 is disabled, go round the loop up to three +times, looking for A6 and AAAA records the first two times. However, unless +doing standalone testing, we force an IPv4 lookup if the domain matches +dns_ipv4_lookup is set. Since A6 records look like being abandoned, support +them only if explicitly configured to do so. On an IPv4 system, go round the +loop once only, looking only for A records. */ #if HAVE_IPV6 - #ifndef STAND_ALONE - if (dns_ipv4_lookup != NULL && + if (disable_ipv6 || (dns_ipv4_lookup != NULL && match_isinlist(host->name, &dns_ipv4_lookup, 0, NULL, NULL, MCL_DOMAIN, - TRUE, NULL) == OK) + TRUE, NULL) == OK)) i = 0; /* look up A records only */ else #endif /* STAND_ALONE */ @@ -2234,8 +2525,10 @@ if ((whichrrs & HOST_FIND_BY_SRV) != 0) if (rc == DNS_FAIL || rc == DNS_AGAIN) { + #ifndef STAND_ALONE if (match_isinlist(host->name, &srv_fail_domains, 0, NULL, NULL, MCL_DOMAIN, TRUE, NULL) != OK) + #endif return HOST_FIND_AGAIN; DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA " "(domain in srv_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN"); @@ -2256,8 +2549,10 @@ if (rc != DNS_SUCCEED && (whichrrs & HOST_FIND_BY_MX) != 0) if (rc == DNS_NOMATCH) return HOST_FIND_FAILED; if (rc == DNS_FAIL || rc == DNS_AGAIN) { + #ifndef STAND_ALONE if (match_isinlist(host->name, &mx_fail_domains, 0, NULL, NULL, MCL_DOMAIN, TRUE, NULL) != OK) + #endif return HOST_FIND_AGAIN; DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA " "(domain in mx_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN"); @@ -2603,10 +2898,14 @@ for (rr = dns_next_rr(&dnsa, &dnss, RESET_ADDITIONAL); if (rr->type != T_A #if HAVE_IPV6 - && rr->type != T_AAAA - #ifdef SUPPORT_A6 - && rr->type != T_A6 - #endif + && ( disable_ipv6 || + ( + rr->type != T_AAAA + #ifdef SUPPORT_A6 + && rr->type != T_A6 + #endif + ) + ) #endif ) continue; @@ -2903,18 +3202,6 @@ return yield; #ifdef STAND_ALONE -BOOL alldigits(uschar *buffer) -{ -if (!isdigit(*buffer)) return FALSE; -if (*buffer == '0' && buffer[1] == 'x') - { - buffer++; - while (isxdigit(*(++buffer))); - } -else while (isdigit(*(++buffer))); -return (*buffer == 0); -} - int main(int argc, char **cargv) { host_item h; @@ -2970,6 +3257,12 @@ while (Ufgets(buffer, 256, stdin) != NULL) else if (Ustrcmp(buffer, "no_qualify_single") == 0) qualify_single = FALSE; else if (Ustrcmp(buffer, "search_parents") == 0) search_parents = TRUE; else if (Ustrcmp(buffer, "no_search_parents") == 0) search_parents = FALSE; + else if (Ustrcmp(buffer, "test_harness") == 0) + running_in_test_harness = !running_in_test_harness; + else if (Ustrcmp(buffer, "res_debug") == 0) + { + _res.options ^= RES_DEBUG; + } else if (Ustrncmp(buffer, "retrans", 7) == 0) { (void)sscanf(CS(buffer+8), "%d", &dns_retrans); @@ -2980,12 +3273,6 @@ while (Ufgets(buffer, 256, stdin) != NULL) (void)sscanf(CS(buffer+6), "%d", &dns_retry); _res.retry = dns_retry; } - else if (alldigits(buffer)) - { - debug_selector = Ustrtol(buffer, NULL, 0); - _res.options &= ~RES_DEBUG; - DEBUG(D_resolver) _res.options |= RES_DEBUG; - } else { int flags = whichrrs;