diff options
Diffstat (limited to 'src/host.c')
-rw-r--r-- | src/host.c | 3431 |
1 files changed, 3431 insertions, 0 deletions
diff --git a/src/host.c b/src/host.c new file mode 100644 index 0000000..e43b507 --- /dev/null +++ b/src/host.c @@ -0,0 +1,3431 @@ +/************************************************* +* Exim - an Internet mail transport agent * +*************************************************/ + +/* Copyright (c) The Exim Maintainers 2020 - 2022 */ +/* Copyright (c) University of Cambridge 1995 - 2018 */ +/* See the file NOTICE for conditions of use and distribution. */ + +/* Functions for finding hosts, either by gethostbyname(), gethostbyaddr(), or +directly via the DNS. When IPv6 is supported, getipnodebyname() and +getipnodebyaddr() may be used instead of gethostbyname() and gethostbyaddr(), +if the newer functions are available. This module also contains various other +functions concerned with hosts and addresses, and a random number function, +used for randomizing hosts with equal MXs but available for use in other parts +of Exim. */ + + +#include "exim.h" + + +/* Static variable for preserving the list of interface addresses in case it is +used more than once. */ + +static ip_address_item *local_interface_data = NULL; + + +#ifdef USE_INET_NTOA_FIX +/************************************************* +* Replacement for broken inet_ntoa() * +*************************************************/ + +/* On IRIX systems, gcc uses a different structure passing convention to the +native libraries. This causes inet_ntoa() to always yield 0.0.0.0 or +255.255.255.255. To get round this, we provide a private version of the +function here. It is used only if USE_INET_NTOA_FIX is set, which should happen +only when gcc is in use on an IRIX system. Code send to me by J.T. Breitner, +with these comments: + + code by Stuart Levy + as seen in comp.sys.sgi.admin + +August 2005: Apparently this is also needed for AIX systems; USE_INET_NTOA_FIX +should now be set for them as well. + +Arguments: sa an in_addr structure +Returns: pointer to static text string +*/ + +char * +inet_ntoa(struct in_addr sa) +{ +static uschar addr[20]; +sprintf(addr, "%d.%d.%d.%d", + (US &sa.s_addr)[0], + (US &sa.s_addr)[1], + (US &sa.s_addr)[2], + (US &sa.s_addr)[3]); + return addr; +} +#endif + + + +/************************************************* +* Random number generator * +*************************************************/ + +/* This is a simple pseudo-random number generator. It does not have to be +very good for the uses to which it is put. When running the regression tests, +start with a fixed seed. + +If you need better, see vaguely_random_number() which is potentially stronger, +if a crypto library is available, but might end up just calling this instead. + +Arguments: + limit: one more than the largest number required + +Returns: a pseudo-random number in the range 0 to limit-1 +*/ + +int +random_number(int limit) +{ +if (limit < 1) + return 0; +if (random_seed == 0) + { + if (f.running_in_test_harness) random_seed = 42; else + { + int p = (int)getpid(); + random_seed = (int)time(NULL) ^ ((p << 16) | p); + } + } +random_seed = 1103515245 * random_seed + 12345; +return (unsigned int)(random_seed >> 16) % limit; +} + +/************************************************* +* Wrappers for logging lookup times * +*************************************************/ + +/* When the 'slow_lookup_log' variable is enabled, these wrappers will +write to the log file all (potential) dns lookups that take more than +slow_lookup_log milliseconds +*/ + +static void +log_long_lookup(const uschar * type, const uschar * data, unsigned long msec) +{ +log_write(0, LOG_MAIN, "Long %s lookup for '%s': %lu msec", + type, data, msec); +} + + +/* returns the current system epoch time in milliseconds. */ +static unsigned long +get_time_in_ms() +{ +struct timeval tmp_time; +unsigned long seconds, microseconds; + +gettimeofday(&tmp_time, NULL); +seconds = (unsigned long) tmp_time.tv_sec; +microseconds = (unsigned long) tmp_time.tv_usec; +return seconds*1000 + microseconds/1000; +} + + +static int +dns_lookup_timerwrap(dns_answer *dnsa, const uschar *name, int type, + const uschar **fully_qualified_name) +{ +int retval; +unsigned long time_msec; + +if (!slow_lookup_log) + return dns_lookup(dnsa, name, type, fully_qualified_name); + +time_msec = get_time_in_ms(); +retval = dns_lookup(dnsa, name, type, fully_qualified_name); +if ((time_msec = get_time_in_ms() - time_msec) > slow_lookup_log) + log_long_lookup(dns_text_type(type), name, time_msec); +return retval; +} + + +/************************************************* +* Replace gethostbyname() when testing * +*************************************************/ + +/* This function is called instead of gethostbyname(), gethostbyname2(), or +getipnodebyname() when running in the test harness. . 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 test harness, this means it will access only the +fake DNS resolver. + +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(const 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; +const uschar *lname = name; +uschar *adds; +uschar **alist; +struct hostent *yield; +dns_answer * dnsa = store_get_dns_answer(); +dns_scan dnss; + +DEBUG(D_host_lookup) + debug_printf("using host_fake_gethostbyname for %s (%s)\n", name, + af == AF_INET ? "IPv4" : "IPv6"); + +/* Handle unqualified "localhost" */ + +if (Ustrcmp(name, "localhost") == 0) + lname = af == AF_INET ? US"127.0.0.1" : US"::1"; + +/* Handle a literal IP address */ + +if ((ipa = string_is_ip_address(lname, NULL)) != 0) + if ( ipa == 4 && af == AF_INET + || ipa == 6 && af == AF_INET6) + { + int x[4]; + yield = store_get(sizeof(struct hostent), GET_UNTAINTED); + alist = store_get(2 * sizeof(char *), GET_UNTAINTED); + adds = store_get(alen, GET_UNTAINTED); + 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; + for (int n = host_aton(lname, x), 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; + yield = NULL; + goto out; + } + +/* Handle a host name */ + +else + { + int type = af == AF_INET ? T_A:T_AAAA; + int rc = dns_lookup_timerwrap(dnsa, lname, type, NULL); + int count = 0; + + lookup_dnssec_authenticated = NULL; + + switch(rc) + { + case DNS_SUCCEED: break; + case DNS_NOMATCH: *error_num = HOST_NOT_FOUND; yield = NULL; goto out; + case DNS_NODATA: *error_num = NO_DATA; yield = NULL; goto out; + case DNS_AGAIN: *error_num = TRY_AGAIN; yield = NULL; goto out; + default: + case DNS_FAIL: *error_num = NO_RECOVERY; yield = NULL; goto out; + } + + for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); + rr; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == type) + count++; + + yield = store_get(sizeof(struct hostent), GET_UNTAINTED); + alist = store_get((count + 1) * sizeof(char *), GET_UNTAINTED); + adds = store_get(count *alen, GET_UNTAINTED); + + yield->h_name = CS name; + yield->h_aliases = NULL; + yield->h_addrtype = af; + yield->h_length = alen; + yield->h_addr_list = CSS alist; + + for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); + rr; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == type) + { + int x[4]; + dns_address *da; + if (!(da = dns_address_from_rr(dnsa, rr))) break; + *alist++ = adds; + for (int n = host_aton(da->address, x), 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; + } + +out: + +store_free_dns_answer(dnsa); +return yield; +} + + + +/************************************************* +* Build chain of host items from list * +*************************************************/ + +/* This function builds a chain of host items from a textual list of host +names. It does not do any lookups. If randomize is true, the chain is build in +a randomized order. There may be multiple groups of independently randomized +hosts; they are delimited by a host name consisting of just "+". + +Arguments: + anchor anchor for the chain + list text list + randomize TRUE for randomizing + +Returns: nothing +*/ + +void +host_build_hostlist(host_item **anchor, const uschar *list, BOOL randomize) +{ +int sep = 0; +int fake_mx = MX_NONE; /* This value is actually -1 */ +uschar *name; + +if (!list) return; +if (randomize) fake_mx--; /* Start at -2 for randomizing */ + +*anchor = NULL; + +while ((name = string_nextinlist(&list, &sep, NULL, 0))) + { + host_item *h; + + if (name[0] == '+' && name[1] == 0) /* "+" delimits a randomized group */ + { /* ignore if not randomizing */ + if (randomize) fake_mx--; + continue; + } + + h = store_get(sizeof(host_item), GET_UNTAINTED); + h->name = name; + h->address = NULL; + h->port = PORT_NONE; + h->mx = fake_mx; + h->sort_key = randomize ? (-fake_mx)*1000 + random_number(1000) : 0; + h->status = hstatus_unknown; + h->why = hwhy_unknown; + h->last_try = 0; + + if (!*anchor) + { + h->next = NULL; + *anchor = h; + } + else + { + host_item *hh = *anchor; + if (h->sort_key < hh->sort_key) + { + h->next = hh; + *anchor = h; + } + else + { + while (hh->next && h->sort_key >= hh->next->sort_key) + hh = hh->next; + h->next = hh->next; + hh->next = h; + } + } + } +} + + + + + +/************************************************* +* Extract port from address string * +*************************************************/ + +/* In the -oMa and -oMi options, a host plus port is given as an IP address +followed by a dot and a port number. This function decodes this. + +An alternative format for the -oMa and -oMi options is [ip address]:port which +is what Exim uses for output, because it seems to becoming commonly used, +whereas the dot form confuses some programs/people. So we recognize that form +too. + +The spool file used to use the first form, but this breaks with a v4mapped ipv6 +hybrid, because the parsing here is not clever. So for spool we now use the +second form. + +Argument: + address points to the string; if there is a port, the '.' in the string + is overwritten with zero to terminate the address; if the string + is in the [xxx]:ppp format, the address is shifted left and the + brackets are removed + +Returns: 0 if there is no port, else the port number. If there's a syntax + error, leave the incoming address alone, and return 0. +*/ + +int +host_address_extract_port(uschar *address) +{ +int port = 0; +uschar *endptr; + +/* Handle the "bracketed with colon on the end" format */ + +if (*address == '[') + { + uschar *rb = address + 1; + while (*rb != 0 && *rb != ']') rb++; + if (*rb++ == 0) return 0; /* Missing ]; leave invalid address */ + if (*rb == ':') + { + port = Ustrtol(rb + 1, &endptr, 10); + if (*endptr != 0) return 0; /* Invalid port; leave invalid address */ + } + else if (*rb != 0) return 0; /* Bad syntax; leave invalid address */ + memmove(address, address + 1, rb - address - 2); + rb[-2] = 0; + } + +/* Handle the "dot on the end" format */ + +else + { + int skip = -3; /* Skip 3 dots in IPv4 addresses */ + address--; + while (*(++address) != 0) + { + int ch = *address; + if (ch == ':') skip = 0; /* Skip 0 dots in IPv6 addresses */ + else if (ch == '.' && skip++ >= 0) break; + } + if (*address == 0) return 0; + port = Ustrtol(address + 1, &endptr, 10); + if (*endptr != 0) return 0; /* Invalid port; leave invalid address */ + *address = 0; + } + +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) +{ +const 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 */ + +/************************************************* +* Build sender_fullhost and sender_rcvhost * +*************************************************/ + +/* This function is called when sender_host_name and/or sender_helo_name +have been set. Or might have been set - for a local message read off the spool +they won't be. In that case, do nothing. Otherwise, set up the fullhost string +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]" 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 +address. + +This function also builds sender_rcvhost for use in Received: lines, whose +syntax is a bit different. This value also includes the RFC 1413 identity. +There wouldn't be two different variables if I had got all this right in the +first place. + +Because this data may survive over more than one incoming SMTP message, it has +to be in permanent store. However, STARTTLS has to be forgotten and redone +on a multi-message conn, so this will be called once per message then. Hence +we use malloc, so we can free. + +Arguments: none +Returns: nothing +*/ + +void +host_build_sender_fullhost(void) +{ +BOOL show_helo = TRUE; +uschar * address, * fullhost, * rcvhost; +rmark reset_point; +int len; + +if (!sender_host_address) return; + +reset_point = store_mark(); + +/* Set up address, with or without the port. After discussion, it seems that +the only format that doesn't cause trouble is [aaaa]:pppp. However, we can't +use this directly as the first item for Received: because it ain't an RFC 2822 +domain. Sigh. */ + +address = string_sprintf("[%s]:%d", sender_host_address, sender_host_port); +if (!LOGGING(incoming_port) || sender_host_port <= 0) + *(Ustrrchr(address, ':')) = 0; + +/* If there's no EHLO/HELO data, we can't show it. */ + +if (!sender_helo_name) 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 canonicalize 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) + { + uschar *portptr = Ustrstr(address, "]:"); + gstring * g; + int adlen; /* Sun compiler doesn't like ++ in initializers */ + + adlen = portptr ? (++portptr - address) : Ustrlen(address); + fullhost = sender_helo_name + ? string_sprintf("(%s) %s", sender_helo_name, address) + : address; + + g = string_catn(NULL, address, adlen); + + if (sender_ident || show_helo || portptr) + { + int firstptr; + g = string_catn(g, US" (", 2); + firstptr = g->ptr; + + if (portptr) + g = string_append(g, 2, US"port=", portptr + 1); + + if (show_helo) + g = string_append(g, 2, + firstptr == g->ptr ? US"helo=" : US" helo=", sender_helo_name); + + if (sender_ident) + g = string_append(g, 2, + firstptr == g->ptr ? US"ident=" : US" ident=", sender_ident); + + g = string_catn(g, US")", 1); + } + + rcvhost = string_from_gstring(g); + } + +/* 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 + { + if (show_helo && strcmpic(sender_host_name, sender_helo_name) == 0) + show_helo = FALSE; + + if (show_helo) + { + fullhost = string_sprintf("%s (%s) %s", sender_host_name, + sender_helo_name, address); + rcvhost = sender_ident + ? string_sprintf("%s\n\t(%s helo=%s ident=%s)", sender_host_name, + address, sender_helo_name, sender_ident) + : string_sprintf("%s (%s helo=%s)", sender_host_name, + address, sender_helo_name); + } + else + { + fullhost = string_sprintf("%s %s", sender_host_name, address); + rcvhost = sender_ident + ? string_sprintf("%s (%s ident=%s)", sender_host_name, address, + sender_ident) + : string_sprintf("%s (%s)", sender_host_name, address); + } + } + +sender_fullhost = string_copy_perm(fullhost, TRUE); +sender_rcvhost = string_copy_perm(rcvhost, TRUE); + +store_reset(reset_point); + +DEBUG(D_host_lookup) debug_printf("sender_fullhost = %s\n", sender_fullhost); +DEBUG(D_host_lookup) debug_printf("sender_rcvhost = %s\n", sender_rcvhost); +} + + + +/************************************************* +* Build host+ident message * +*************************************************/ + +/* Used when logging rejections and various ACL and SMTP incidents. The text +return depends on whether sender_fullhost and sender_ident are set or not: + + no ident, no host => U=unknown + no ident, host set => H=sender_fullhost + ident set, no host => U=ident + ident set, host set => H=sender_fullhost U=ident + +Use taint-unchecked routines on the assumption we'll never expand the results. + +Arguments: + useflag TRUE if first item to be flagged (H= or U=); if there are two + items, the second is always flagged + +Returns: pointer to a string in big_buffer +*/ + +uschar * +host_and_ident(BOOL useflag) +{ +if (!sender_fullhost) + string_format_nt(big_buffer, big_buffer_size, "%s%s", useflag ? "U=" : "", + sender_ident ? sender_ident : US"unknown"); +else + { + uschar * flag = useflag ? US"H=" : US""; + uschar * iface = US""; + if (LOGGING(incoming_interface) && interface_address) + iface = string_sprintf(" I=[%s]:%d", interface_address, interface_port); + if (sender_ident) + string_format_nt(big_buffer, big_buffer_size, "%s%s%s U=%s", + flag, sender_fullhost, iface, sender_ident); + else + string_format_nt(big_buffer, big_buffer_size, "%s%s%s", + flag, sender_fullhost, iface); + } +return big_buffer; +} + +#endif /* STAND_ALONE */ + + + + +/************************************************* +* Build list of local interfaces * +*************************************************/ + +/* This function interprets the contents of the local_interfaces or +extra_local_interfaces options, and creates an ip_address_item block for each +item on the list. There is no special interpretation of any IP addresses; in +particular, 0.0.0.0 and ::0 are returned without modification. If any address +includes a port, it is set in the block. Otherwise the port value is set to +zero. + +Arguments: + list the list + name the name of the option being expanded + +Returns: a chain of ip_address_items, each containing to a textual + version of an IP address, and a port number (host order) or + zero if no port was given with the address +*/ + +ip_address_item * +host_build_ifacelist(const uschar *list, uschar *name) +{ +int sep = 0; +uschar *s; +ip_address_item * yield = NULL, * last = NULL, * next; + +while ((s = string_nextinlist(&list, &sep, NULL, 0))) + { + int ipv; + int port = host_address_extract_port(s); /* Leaves just the IP address */ + + if (!(ipv = string_is_ip_address(s, NULL))) + 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. */ + + next = store_get(sizeof(ip_address_item), list); + next->next = NULL; + Ustrcpy(next->address, s); + next->port = port; + next->v6_include_v4 = FALSE; + next->log = NULL; + + if (!yield) + yield = last = next; + else + { + last->next = next; + last = next; + } + } + +return yield; +} + + + + + +/************************************************* +* Find addresses on local interfaces * +*************************************************/ + +/* This function finds the addresses of local IP interfaces. These are used +when testing for routing to the local host. As the function may be called more +than once, the list is preserved in permanent store, pointed to by a static +variable, to save doing the work more than once per process. + +The generic list of interfaces is obtained by calling host_build_ifacelist() +for local_interfaces and extra_local_interfaces. This list scanned to remove +duplicates (which may exist with different ports - not relevant here). If +either of the wildcard IP addresses (0.0.0.0 and ::0) are encountered, they are +replaced by the appropriate (IPv4 or IPv6) list of actual local interfaces, +obtained from os_find_running_interfaces(). + +Arguments: none +Returns: a chain of ip_address_items, each containing to a textual + version of an IP address; the port numbers are not relevant +*/ + + +/* First, a local subfunction to add an interface to a list in permanent store, +but only if there isn't a previous copy of that address on the list. */ + +static ip_address_item * +add_unique_interface(ip_address_item *list, ip_address_item *ipa) +{ +ip_address_item *ipa2; +for (ipa2 = list; ipa2; ipa2 = ipa2->next) + if (Ustrcmp(ipa2->address, ipa->address) == 0) return list; +ipa2 = store_get_perm(sizeof(ip_address_item), FALSE); +*ipa2 = *ipa; +ipa2->next = list; +return ipa2; +} + + +/* This is the globally visible function */ + +ip_address_item * +host_find_interfaces(void) +{ +ip_address_item *running_interfaces = NULL; + +if (!local_interface_data) + { + void *reset_item = store_mark(); + ip_address_item *dlist = host_build_ifacelist(CUS local_interfaces, + US"local_interfaces"); + ip_address_item *xlist = host_build_ifacelist(CUS extra_local_interfaces, + US"extra_local_interfaces"); + ip_address_item *ipa; + + if (!dlist) dlist = xlist; + else + { + for (ipa = dlist; ipa->next; ipa = ipa->next) ; + ipa->next = xlist; + } + + for (ipa = dlist; ipa; ipa = ipa->next) + { + if (Ustrcmp(ipa->address, "0.0.0.0") == 0 || + Ustrcmp(ipa->address, "::0") == 0) + { + BOOL ipv6 = ipa->address[0] == ':'; + if (!running_interfaces) + running_interfaces = os_find_running_interfaces(); + for (ip_address_item * ipa2 = running_interfaces; ipa2; ipa2 = ipa2->next) + if ((Ustrchr(ipa2->address, ':') != NULL) == ipv6) + local_interface_data = add_unique_interface(local_interface_data, + ipa2); + } + else + { + local_interface_data = add_unique_interface(local_interface_data, ipa); + DEBUG(D_interface) + { + debug_printf("Configured local interface: address=%s", ipa->address); + if (ipa->port != 0) debug_printf(" port=%d", ipa->port); + debug_printf("\n"); + } + } + } + store_reset(reset_item); + } + +return local_interface_data; +} + + + + + +/************************************************* +* Convert network IP address to text * +*************************************************/ + +/* Given an IPv4 or IPv6 address in binary, convert it to a text +string and return the result in a piece of new store. The address can +either be given directly, or passed over in a sockaddr structure. Note +that this isn't the converse of host_aton() because of byte ordering +differences. See host_nmtoa() below. + +Arguments: + type if < 0 then arg points to a sockaddr, else + either AF_INET or AF_INET6 + arg points to a sockaddr if type is < 0, or + points to an IPv4 address (32 bits), or + points to an IPv6 address (128 bits), + in both cases, in network byte order + buffer if NULL, the result is returned in gotten store; + else points to a buffer to hold the answer + portptr points to where to put the port number, if non NULL; only + used when type < 0 + +Returns: pointer to character string +*/ + +uschar * +host_ntoa(int type, const void *arg, uschar *buffer, int *portptr) +{ +uschar *yield; + +/* The new world. It is annoying that we have to fish out the address from +different places in the block, depending on what kind of address it is. It +is also a pain that inet_ntop() returns a const uschar *, whereas the IPv4 +function inet_ntoa() returns just uschar *, and some picky compilers insist +on warning if one assigns a const uschar * to a uschar *. Hence the casts. */ + +#if HAVE_IPV6 +uschar addr_buffer[46]; +if (type < 0) + { + int family = ((struct sockaddr *)arg)->sa_family; + if (family == AF_INET6) + { + struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg; + yield = US inet_ntop(family, &(sk->sin6_addr), CS addr_buffer, + sizeof(addr_buffer)); + if (portptr) *portptr = ntohs(sk->sin6_port); + } + else + { + struct sockaddr_in *sk = (struct sockaddr_in *)arg; + yield = US inet_ntop(family, &(sk->sin_addr), CS addr_buffer, + sizeof(addr_buffer)); + if (portptr) *portptr = ntohs(sk->sin_port); + } + } +else + { + yield = US inet_ntop(type, arg, CS addr_buffer, sizeof(addr_buffer)); + } + +/* If the result is a mapped IPv4 address, show it in V4 format. */ + +if (Ustrncmp(yield, "::ffff:", 7) == 0) yield += 7; + +#else /* HAVE_IPV6 */ + +/* The old world */ + +if (type < 0) + { + yield = US inet_ntoa(((struct sockaddr_in *)arg)->sin_addr); + if (portptr) *portptr = ntohs(((struct sockaddr_in *)arg)->sin_port); + } +else + yield = US inet_ntoa(*((struct in_addr *)arg)); +#endif + +/* If there is no buffer, put the string into some new store. */ + +if (!buffer) buffer = store_get(46, GET_UNTAINTED); + +/* Callers of this function with a non-NULL buffer must ensure that it is +large enough to hold an IPv6 address, namely, at least 46 bytes. That's what +makes this use of strcpy() OK. +If the library returned apparently an apparently tainted string, clean it; +we trust IP addresses. */ + +string_format_nt(buffer, 46, "%s", yield); +return buffer; +} + + + + +/************************************************* +* Convert address text to binary * +*************************************************/ + +/* Given the textual form of an IP address, convert it to binary in an +array of ints. IPv4 addresses occupy one int; IPv6 addresses occupy 4 ints. +The result has the first byte in the most significant byte of the first int. In +other words, the result is not in network byte order, but in host byte order. +As a result, this is not the converse of host_ntoa(), which expects network +byte order. See host_nmtoa() below. + +Arguments: + address points to the textual address, checked for syntax + bin points to an array of 4 ints + +Returns: the number of ints used +*/ + +int +host_aton(const uschar *address, int *bin) +{ +int x[4]; +int v4offset = 0; + +/* Handle IPv6 address, which may end with an IPv4 address. It may also end +with a "scope", introduced by a percent sign. This code is NOT enclosed in #if +HAVE_IPV6 in order that IPv6 addresses are recognized even if IPv6 is not +supported. */ + +if (Ustrchr(address, ':') != NULL) + { + const uschar *p = address; + const uschar *component[8]; + BOOL ipv4_ends = FALSE; + int ci = 0; + int nulloffset = 0; + int v6count = 8; + int i; + + /* If the address starts with a colon, it will start with two colons. + Just lose the first one, which will leave a null first component. */ + + if (*p == ':') p++; + + /* Split the address into components separated by colons. The input address + is supposed to be checked for syntax. There was a case where this was + overlooked; to guard against that happening again, check here and crash if + there are too many components. */ + + while (*p != 0 && *p != '%') + { + int len = Ustrcspn(p, ":%"); + if (len == 0) nulloffset = ci; + if (ci > 7) log_write(0, LOG_MAIN|LOG_PANIC_DIE, + "Internal error: invalid IPv6 address \"%s\" passed to host_aton()", + address); + component[ci++] = p; + p += len; + if (*p == ':') p++; + } + + /* If the final component contains a dot, it is a trailing v4 address. + As the syntax is known to be checked, just set up for a trailing + v4 address and restrict the v6 part to 6 components. */ + + if (Ustrchr(component[ci-1], '.') != NULL) + { + address = component[--ci]; + ipv4_ends = TRUE; + v4offset = 3; + v6count = 6; + } + + /* If there are fewer than 6 or 8 components, we have to insert some + more empty ones in the middle. */ + + if (ci < v6count) + { + int insert_count = v6count - ci; + for (i = v6count-1; i > nulloffset + insert_count; i--) + component[i] = component[i - insert_count]; + while (i > nulloffset) component[i--] = US""; + } + + /* Now turn the components into binary in pairs and bung them + into the vector of ints. */ + + for (i = 0; i < v6count; i += 2) + bin[i/2] = (Ustrtol(component[i], NULL, 16) << 16) + + Ustrtol(component[i+1], NULL, 16); + + /* If there was no terminating v4 component, we are done. */ + + if (!ipv4_ends) return 4; + } + +/* Handle IPv4 address */ + +(void)sscanf(CS address, "%d.%d.%d.%d", x, x+1, x+2, x+3); +bin[v4offset] = ((uint)x[0] << 24) + (x[1] << 16) + (x[2] << 8) + x[3]; +return v4offset+1; +} + + +/************************************************* +* Apply mask to an IP address * +*************************************************/ + +/* Mask an address held in 1 or 4 ints, with the ms bit in the ms bit of the +first int, etc. + +Arguments: + count the number of ints + binary points to the ints to be masked + mask the count of ms bits to leave, or -1 if no masking + +Returns: nothing +*/ + +void +host_mask(int count, int *binary, int mask) +{ +if (mask < 0) mask = 99999; +for (int i = 0; i < count; i++) + { + int wordmask; + if (mask == 0) wordmask = 0; + else if (mask < 32) + { + wordmask = (uint)(-1) << (32 - mask); + mask = 0; + } + else + { + wordmask = -1; + mask -= 32; + } + binary[i] &= wordmask; + } +} + + + + +/************************************************* +* Convert masked IP address in ints to text * +*************************************************/ + +/* We can't use host_ntoa() because it assumes the binary values are in network +byte order, and these are the result of host_aton(), which puts them in ints in +host byte order. Also, we really want IPv6 addresses to be in a canonical +format, so we output them with no abbreviation. In a number of cases we can't +use the normal colon separator in them because it terminates keys in lsearch +files, so we want to use dot instead. There's an argument that specifies what +to use for IPv6 addresses. + +Arguments: + count 1 or 4 (number of ints) + binary points to the ints + mask mask value; if < 0 don't add to result + buffer big enough to hold the result + sep component separator character for IPv6 addresses + +Returns: the number of characters placed in buffer, not counting + the final nul. +*/ + +int +host_nmtoa(int count, int *binary, int mask, uschar *buffer, int sep) +{ +int j; +uschar *tt = buffer; + +if (count == 1) + { + j = binary[0]; + for (int i = 24; i >= 0; i -= 8) + tt += sprintf(CS tt, "%d.", (j >> i) & 255); + } +else + for (int i = 0; i < 4; i++) + { + j = binary[i]; + tt += sprintf(CS tt, "%04x%c%04x%c", (j >> 16) & 0xffff, sep, j & 0xffff, sep); + } + +tt--; /* lose final separator */ + +if (mask < 0) + *tt = 0; +else + tt += sprintf(CS tt, "/%d", mask); + +return tt - buffer; +} + + +/* Like host_nmtoa() but: ipv6-only, canonical output, no mask + +Arguments: + binary points to the ints + buffer big enough to hold the result + +Returns: the number of characters placed in buffer, not counting + the final nul. +*/ + +int +ipv6_nmtoa(int * binary, uschar * buffer) +{ +int i, j, k; +uschar * c = buffer; +uschar * d = NULL; /* shut insufficiently "clever" compiler up */ + +for (i = 0; i < 4; i++) + { /* expand to text */ + j = binary[i]; + c += sprintf(CS c, "%x:%x:", (j >> 16) & 0xffff, j & 0xffff); + } + +for (c = buffer, k = -1, i = 0; i < 8; i++) + { /* find longest 0-group sequence */ + if (*c == '0') /* must be "0:" */ + { + uschar * s = c; + j = i; + while (c[2] == '0') i++, c += 2; + if (i-j > k) + { + k = i-j; /* length of sequence */ + d = s; /* start of sequence */ + } + } + while (*++c != ':') ; + c++; + } + +*--c = '\0'; /* drop trailing colon */ + +/* debug_printf("%s: D k %d <%s> <%s>\n", __FUNCTION__, k, buffer, buffer + 2*(k+1)); */ +if (k >= 0) + { /* collapse */ + c = d + 2*(k+1); + if (d == buffer) c--; /* need extra colon */ + *d++ = ':'; /* 1st 0 */ + while ((*d++ = *c++)) ; + } +else + d = c; + +return d - buffer; +} + + + +/************************************************* +* Check port for tls_on_connect * +*************************************************/ + +/* This function checks whether a given incoming port is configured for tls- +on-connect. It is called from the daemon and from inetd handling. If the global +option tls_on_connect is already set, all ports operate this way. Otherwise, we +check the tls_on_connect_ports option for a list of ports. + +Argument: a port number +Returns: TRUE or FALSE +*/ + +BOOL +host_is_tls_on_connect_port(int port) +{ +int sep = 0; +const uschar * list = tls_in.on_connect_ports; + +if (tls_in.on_connect) return TRUE; + +for (uschar * s, * end; s = string_nextinlist(&list, &sep, NULL, 0); ) + if (Ustrtol(s, &end, 10) == port) + return TRUE; + +return FALSE; +} + + + +/************************************************* +* Check whether host is in a network * +*************************************************/ + +/* This function checks whether a given IP address matches a pattern that +represents either a single host, or a network (using CIDR notation). The caller +of this function must check the syntax of the arguments before calling it. + +Arguments: + host string representation of the ip-address to check + net string representation of the network, with optional CIDR mask + maskoffset offset to the / that introduces the mask in the key + zero if there is no mask + +Returns: + TRUE the host is inside the network + FALSE the host is NOT inside the network +*/ + +BOOL +host_is_in_net(const uschar *host, const uschar *net, int maskoffset) +{ +int address[4]; +int incoming[4]; +int mlen; +int size = host_aton(net, address); +int insize; + +/* No mask => all bits to be checked */ + +if (maskoffset == 0) mlen = 99999; /* Big number */ + else mlen = Uatoi(net + maskoffset + 1); + +/* Convert the incoming address to binary. */ + +insize = host_aton(host, incoming); + +/* Convert IPv4 addresses given in IPv6 compatible mode, which represent + connections from IPv4 hosts to IPv6 hosts, that is, addresses of the form + ::ffff:<v4address>, to IPv4 format. */ + +if (insize == 4 && incoming[0] == 0 && incoming[1] == 0 && + incoming[2] == 0xffff) + { + insize = 1; + incoming[0] = incoming[3]; + } + +/* No match if the sizes don't agree. */ + +if (insize != size) return FALSE; + +/* Else do the masked comparison. */ + +for (int i = 0; i < size; i++) + { + int mask; + if (mlen == 0) mask = 0; + else if (mlen < 32) + { + mask = (uint)(-1) << (32 - mlen); + mlen = 0; + } + else + { + mask = -1; + mlen -= 32; + } + if ((incoming[i] & mask) != (address[i] & mask)) return FALSE; + } + +return TRUE; +} + + + +/************************************************* +* Scan host list for local hosts * +*************************************************/ + +/* Scan through a chain of addresses and check whether any of them is the +address of an interface on the local machine. If so, remove that address and +any previous ones with the same MX value, and all subsequent ones (which will +have greater or equal MX values) from the chain. Note: marking them as unusable +is NOT the right thing to do because it causes the hosts not to be used for +other domains, for which they may well be correct. + +The hosts may be part of a longer chain; we only process those between the +initial pointer and the "last" pointer. + +There is also a list of "pseudo-local" host names which are checked against the +host names. Any match causes that host item to be treated the same as one which +matches a local IP address. + +If the very first host is a local host, then all MX records had a precedence +greater than or equal to that of the local host. Either there's a problem in +the DNS, or an apparently remote name turned out to be an abbreviation for the +local host. Give a specific return code, and let the caller decide what to do. +Otherwise, give a success code if at least one host address has been found. + +Arguments: + host pointer to the first host in the chain + lastptr pointer to pointer to the last host in the chain (may be updated) + removed if not NULL, set TRUE if some local addresses were removed + from the list + +Returns: + HOST_FOUND if there is at least one host with an IP address on the chain + and an MX value less than any MX value associated with the + local host + HOST_FOUND_LOCAL if a local host is among the lowest-numbered MX hosts; when + the host addresses were obtained from A records or + gethostbyname(), the MX values are set to -1. + HOST_FIND_FAILED if no valid hosts with set IP addresses were found +*/ + +int +host_scan_for_local_hosts(host_item *host, host_item **lastptr, BOOL *removed) +{ +int yield = HOST_FIND_FAILED; +host_item *last = *lastptr; +host_item *prev = NULL; +host_item *h; + +if (removed != NULL) *removed = FALSE; + +if (local_interface_data == NULL) local_interface_data = host_find_interfaces(); + +for (h = host; h != last->next; h = h->next) + { + #ifndef STAND_ALONE + if (hosts_treat_as_local != NULL) + { + int rc; + const uschar *save = deliver_domain; + deliver_domain = h->name; /* set $domain */ + rc = match_isinlist(string_copylc(h->name), CUSS &hosts_treat_as_local, 0, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL); + deliver_domain = save; + if (rc == OK) goto FOUND_LOCAL; + } + #endif + + /* It seems that on many operating systems, 0.0.0.0 is treated as a synonym + for 127.0.0.1 and refers to the local host. We therefore force it always to + be treated as local. */ + + if (h->address != NULL) + { + if (Ustrcmp(h->address, "0.0.0.0") == 0) goto FOUND_LOCAL; + for (ip_address_item * ip = local_interface_data; ip; ip = ip->next) + if (Ustrcmp(h->address, ip->address) == 0) goto FOUND_LOCAL; + yield = HOST_FOUND; /* At least one remote address has been found */ + } + + /* Update prev to point to the last host item before any that have + the same MX value as the one we have just considered. */ + + if (h->next == NULL || h->next->mx != h->mx) prev = h; + } + +return yield; /* No local hosts found: return HOST_FOUND or HOST_FIND_FAILED */ + +/* A host whose IP address matches a local IP address, or whose name matches +something in hosts_treat_as_local has been found. */ + +FOUND_LOCAL: + +if (prev == NULL) + { + HDEBUG(D_host_lookup) debug_printf((h->mx >= 0)? + "local host has lowest MX\n" : + "local host found for non-MX address\n"); + return HOST_FOUND_LOCAL; + } + +HDEBUG(D_host_lookup) + { + debug_printf("local host in host list - removed hosts:\n"); + for (h = prev->next; h != last->next; h = h->next) + debug_printf(" %s %s %d\n", h->name, h->address, h->mx); + } + +if (removed != NULL) *removed = TRUE; +prev->next = last->next; +*lastptr = prev; +return yield; +} + + + + +/************************************************* +* Remove duplicate IPs in host list * +*************************************************/ + +/* You would think that administrators could set up their DNS records so that +one ended up with a list of unique IP addresses after looking up A or MX +records, but apparently duplication is common. So we scan such lists and +remove the later duplicates. Note that we may get lists in which some host +addresses are not set. + +Arguments: + host pointer to the first host in the chain + lastptr pointer to pointer to the last host in the chain (may be updated) + +Returns: nothing +*/ + +static void +host_remove_duplicates(host_item *host, host_item **lastptr) +{ +while (host != *lastptr) + { + if (host->address != NULL) + { + host_item *h = host; + while (h != *lastptr) + { + if (h->next->address != NULL && + Ustrcmp(h->next->address, host->address) == 0) + { + DEBUG(D_host_lookup) debug_printf("duplicate IP address %s (MX=%d) " + "removed\n", host->address, h->next->mx); + if (h->next == *lastptr) *lastptr = h; + h->next = h->next->next; + } + else h = h->next; + } + } + /* If the last item was removed, host may have become == *lastptr */ + if (host != *lastptr) host = host->next; + } +} + + + + +/************************************************* +* Find sender host name by gethostbyaddr() * +*************************************************/ + +/* This used to be the only way it was done, but it turns out that not all +systems give aliases for calls to gethostbyaddr() - or one of the modern +equivalents like getipnodebyaddr(). Fortunately, multiple PTR records are rare, +but they can still exist. This function is now used only when a DNS lookup of +the IP address fails, in order to give access to /etc/hosts. + +Arguments: none +Returns: OK, DEFER, FAIL +*/ + +static int +host_name_lookup_byaddr(void) +{ +struct hostent * hosts; +struct in_addr addr; +unsigned long time_msec = 0; /* init to quieten dumb static analysis */ + +if (slow_lookup_log) time_msec = get_time_in_ms(); + +/* Lookup on IPv6 system */ + +#if HAVE_IPV6 +if (Ustrchr(sender_host_address, ':') != NULL) + { + struct in6_addr addr6; + if (inet_pton(AF_INET6, CS sender_host_address, &addr6) != 1) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unable to parse \"%s\" as an " + "IPv6 address", sender_host_address); + #if HAVE_GETIPNODEBYADDR + hosts = getipnodebyaddr(CS &addr6, sizeof(addr6), AF_INET6, &h_errno); + #else + hosts = gethostbyaddr(CS &addr6, sizeof(addr6), AF_INET6); + #endif + } +else + { + if (inet_pton(AF_INET, CS sender_host_address, &addr) != 1) + log_write(0, LOG_MAIN|LOG_PANIC_DIE, "unable to parse \"%s\" as an " + "IPv4 address", sender_host_address); + #if HAVE_GETIPNODEBYADDR + hosts = getipnodebyaddr(CS &addr, sizeof(addr), AF_INET, &h_errno); + #else + hosts = gethostbyaddr(CS &addr, sizeof(addr), AF_INET); + #endif + } + +/* Do lookup on IPv4 system */ + +#else +addr.s_addr = (S_ADDR_TYPE)inet_addr(CS sender_host_address); +hosts = gethostbyaddr(CS(&addr), sizeof(addr), AF_INET); +#endif + +if ( slow_lookup_log + && (time_msec = get_time_in_ms() - time_msec) > slow_lookup_log + ) + log_long_lookup(US"gethostbyaddr", sender_host_address, time_msec); + +/* Failed to look up the host. */ + +if (!hosts) + { + HDEBUG(D_host_lookup) debug_printf("IP address lookup failed: h_errno=%d\n", + h_errno); + return (h_errno == TRY_AGAIN || h_errno == NO_RECOVERY) ? DEFER : FAIL; + } + +/* It seems there are some records in the DNS that yield an empty name. We +treat this as non-existent. In some operating systems, this is returned as an +empty string; in others as a single dot. */ + +if (!hosts->h_name || !hosts->h_name[0] || hosts->h_name[0] == '.') + { + HDEBUG(D_host_lookup) debug_printf("IP address lookup yielded an empty name: " + "treated as non-existent host name\n"); + return FAIL; + } + +/* Copy and lowercase the name, which is in static storage in many systems. +Put it in permanent memory. */ + + { + int old_pool = store_pool; + store_pool = POOL_TAINT_PERM; /* names are tainted */ + + sender_host_name = string_copylc(US hosts->h_name); + + /* If the host has aliases, build a copy of the alias list */ + + if (hosts->h_aliases) + { + int count = 1; /* need 1 more for terminating NULL */ + uschar **ptr; + + for (uschar ** aliases = USS hosts->h_aliases; *aliases; aliases++) count++; + store_pool = POOL_PERM; + ptr = sender_host_aliases = store_get(count * sizeof(uschar *), GET_UNTAINTED); + store_pool = POOL_TAINT_PERM; + + for (uschar ** aliases = USS hosts->h_aliases; *aliases; aliases++) + *ptr++ = string_copylc(*aliases); + *ptr = NULL; + } + store_pool = old_pool; + } + +return OK; +} + + + +/************************************************* +* Find host name for incoming call * +*************************************************/ + +/* Put the name in permanent store, pointed to by sender_host_name. We also set +up a list of alias names, pointed to by sender_host_alias. The list is +NULL-terminated. The incoming address is in sender_host_address, either in +dotted-quad form for IPv4 or in colon-separated form for IPv6. + +This function does a thorough check that the names it finds point back to the +incoming IP address. Any that do not are discarded. Note that this is relied on +by the ACL reverse_host_lookup check. + +On some systems, get{host,ipnode}byaddr() appears to do this internally, but +this it not universally true. Also, for release 4.30, this function was changed +to do a direct DNS lookup first, by default[1], because it turns out that that +is the only guaranteed way to find all the aliases on some systems. My +experiments indicate that Solaris gethostbyaddr() gives the aliases for but +Linux does not. + +[1] The actual order is controlled by the host_lookup_order option. + +Arguments: none +Returns: OK on success, the answer being placed in the global variable + sender_host_name, with any aliases in a list hung off + sender_host_aliases + FAIL if no host name can be found + DEFER if a temporary error was encountered + +The variable host_lookup_msg is set to an empty string on success, or to a +reason for the failure otherwise, in a form suitable for tagging onto an error +message, and also host_lookup_failed is set TRUE if the lookup failed. If there +was a defer, host_lookup_deferred is set TRUE. + +Any dynamically constructed string for host_lookup_msg must be in permanent +store, because it might be used for several incoming messages on the same SMTP +connection. */ + +int +host_name_lookup(void) +{ +int old_pool, rc; +int sep = 0; +uschar *save_hostname; +uschar **aliases; +uschar *ordername; +const uschar *list = host_lookup_order; +dns_answer * dnsa = store_get_dns_answer(); +dns_scan dnss; + +sender_host_dnssec = host_lookup_deferred = host_lookup_failed = FALSE; + +HDEBUG(D_host_lookup) + debug_printf("looking up host name for %s\n", sender_host_address); + +/* For testing the case when a lookup does not complete, we have a special +reserved IP address. */ + +if (f.running_in_test_harness && + Ustrcmp(sender_host_address, "99.99.99.99") == 0) + { + HDEBUG(D_host_lookup) + debug_printf("Test harness: host name lookup returns DEFER\n"); + host_lookup_deferred = TRUE; + return DEFER; + } + +/* Do lookups directly in the DNS or via gethostbyaddr() (or equivalent), in +the order specified by the host_lookup_order option. */ + +while ((ordername = string_nextinlist(&list, &sep, NULL, 0))) + { + if (strcmpic(ordername, US"bydns") == 0) + { + uschar * name = dns_build_reverse(sender_host_address); + + dns_init(FALSE, FALSE, FALSE); /* dnssec ctrl by dns_dnssec_ok glbl */ + rc = dns_lookup_timerwrap(dnsa, name, T_PTR, NULL); + + /* The first record we come across is used for the name; others are + considered to be aliases. We have to scan twice, in order to find out the + number of aliases. However, if all the names are empty, we will behave as + if failure. (PTR records that yield empty names have been encountered in + the DNS.) */ + + if (rc == DNS_SUCCEED) + { + uschar **aptr = NULL; + int ssize = 264; + int count = 1; /* need 1 more for terminating NULL */ + int old_pool = store_pool; + + sender_host_dnssec = dns_is_secure(dnsa); + DEBUG(D_dns) + debug_printf("Reverse DNS security status: %s\n", + sender_host_dnssec ? "DNSSEC verified (AD)" : "unverified"); + + store_pool = POOL_PERM; /* Save names in permanent storage */ + + for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); + rr; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == T_PTR) + count++; + + /* Get store for the list of aliases. For compatibility with + gethostbyaddr, we make an empty list if there are none. */ + + aptr = sender_host_aliases = store_get(count * sizeof(uschar *), GET_UNTAINTED); + + /* Re-scan and extract the names */ + + for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); + rr; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == T_PTR) + { + uschar * s = store_get(ssize, GET_TAINTED); /* names are tainted */ + + /* If an overlong response was received, the data will have been + truncated and dn_expand may fail. */ + + if (dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, + US (rr->data), (DN_EXPAND_ARG4_TYPE)(s), ssize) < 0) + { + log_write(0, LOG_MAIN, "host name alias list truncated for %s", + sender_host_address); + break; + } + + store_release_above(s + Ustrlen(s) + 1); + if (!s[0]) + { + HDEBUG(D_host_lookup) debug_printf("IP address lookup yielded an " + "empty name: treated as non-existent host name\n"); + continue; + } + if (!sender_host_name) sender_host_name = s; + else *aptr++ = s; + while (*s) { *s = tolower(*s); s++; } + } + + *aptr = NULL; /* End of alias list */ + store_pool = old_pool; /* Reset store pool */ + + /* If we've found a name, break out of the "order" loop */ + + if (sender_host_name) break; + } + + /* If the DNS lookup deferred, we must also defer. */ + + if (rc == DNS_AGAIN) + { + HDEBUG(D_host_lookup) + debug_printf("IP address PTR lookup gave temporary error\n"); + host_lookup_deferred = TRUE; + return DEFER; + } + } + + /* Do a lookup using gethostbyaddr() - or equivalent */ + + else if (strcmpic(ordername, US"byaddr") == 0) + { + HDEBUG(D_host_lookup) + debug_printf("IP address lookup using gethostbyaddr()\n"); + rc = host_name_lookup_byaddr(); + if (rc == DEFER) + { + host_lookup_deferred = TRUE; + return rc; /* Can't carry on */ + } + if (rc == OK) break; /* Found a name */ + } + } /* Loop for bydns/byaddr scanning */ + +/* If we have failed to find a name, return FAIL and log when required. +NB host_lookup_msg must be in permanent store. */ + +if (!sender_host_name) + { + if (host_checking || !f.log_testing_mode) + log_write(L_host_lookup_failed, LOG_MAIN, "no host name found for IP " + "address %s", sender_host_address); + host_lookup_msg = US" (failed to find host name from IP address)"; + host_lookup_failed = TRUE; + return FAIL; + } + +HDEBUG(D_host_lookup) + { + uschar **aliases = sender_host_aliases; + debug_printf("IP address lookup yielded \"%s\"\n", sender_host_name); + while (*aliases) debug_printf(" alias \"%s\"\n", *aliases++); + } + +/* We need to verify that a forward lookup on the name we found does indeed +correspond to the address. This is for security: in principle a malefactor who +happened to own a reverse zone could set it to point to any names at all. + +This code was present in versions of Exim before 3.20. At that point I took it +out because I thought that gethostbyaddr() did the check anyway. It turns out +that this isn't always the case, so it's coming back in at 4.01. This version +is actually better, because it also checks aliases. + +The code was made more robust at release 4.21. Prior to that, it accepted all +the names if any of them had the correct IP address. Now the code checks all +the names, and accepts only those that have the correct IP address. */ + +save_hostname = sender_host_name; /* Save for error messages */ +aliases = sender_host_aliases; +for (uschar * hname = sender_host_name; hname; hname = *aliases++) + { + int rc; + BOOL ok = FALSE; + host_item h = { .next = NULL, .name = hname, .mx = MX_NONE, .address = NULL }; + dnssec_domains d = + { .request = sender_host_dnssec ? US"*" : NULL, .require = NULL }; + + if ( (rc = host_find_bydns(&h, NULL, HOST_FIND_BY_A | HOST_FIND_BY_AAAA, + NULL, NULL, NULL, &d, NULL, NULL)) == HOST_FOUND + || rc == HOST_FOUND_LOCAL + ) + { + HDEBUG(D_host_lookup) debug_printf("checking addresses for %s\n", hname); + + /* If the forward lookup was not secure we cancel the is-secure variable */ + + DEBUG(D_dns) debug_printf("Forward DNS security status: %s\n", + h.dnssec == DS_YES ? "DNSSEC verified (AD)" : "unverified"); + if (h.dnssec != DS_YES) sender_host_dnssec = FALSE; + + for (host_item * hh = &h; hh; hh = hh->next) + if (host_is_in_net(hh->address, sender_host_address, 0)) + { + HDEBUG(D_host_lookup) debug_printf(" %s OK\n", hh->address); + ok = TRUE; + break; + } + else + HDEBUG(D_host_lookup) debug_printf(" %s\n", hh->address); + + if (!ok) HDEBUG(D_host_lookup) + debug_printf("no IP address for %s matched %s\n", hname, + sender_host_address); + } + else if (rc == HOST_FIND_AGAIN) + { + HDEBUG(D_host_lookup) debug_printf("temporary error for host name lookup\n"); + host_lookup_deferred = TRUE; + sender_host_name = NULL; + return DEFER; + } + else + HDEBUG(D_host_lookup) debug_printf("no IP addresses found for %s\n", hname); + + /* If this name is no good, and it's the sender name, set it null pro tem; + if it's an alias, just remove it from the list. */ + + if (!ok) + { + if (hname == sender_host_name) sender_host_name = NULL; else + { + uschar **a; /* Don't amalgamate - some */ + a = --aliases; /* compilers grumble */ + while (*a != NULL) { *a = a[1]; a++; } + } + } + } + +/* If sender_host_name == NULL, it means we didn't like the name. Replace +it with the first alias, if there is one. */ + +if (sender_host_name == NULL && *sender_host_aliases != NULL) + sender_host_name = *sender_host_aliases++; + +/* If we now have a main name, all is well. */ + +if (sender_host_name != NULL) return OK; + +/* We have failed to find an address that matches. */ + +HDEBUG(D_host_lookup) + debug_printf("%s does not match any IP address for %s\n", + sender_host_address, save_hostname); + +/* This message must be in permanent store */ + +old_pool = store_pool; +store_pool = POOL_PERM; +host_lookup_msg = string_sprintf(" (%s does not match any IP address for %s)", + sender_host_address, save_hostname); +store_pool = old_pool; +host_lookup_failed = TRUE; +return FAIL; +} + + + + +/************************************************* +* Find IP address(es) for host by name * +*************************************************/ + +/* The input is a host_item structure with the name filled in and the address +field set to NULL. We use gethostbyname() or getipnodebyname() or +gethostbyname2(), as appropriate. Of course, these functions may use the DNS, +but they do not do MX processing. It appears, however, that in some systems the +current setting of resolver options is used when one of these functions calls +the resolver. For this reason, we call dns_init() at the start, with arguments +influenced by bits in "flags", just as we do for host_find_bydns(). + +The second argument provides a host list (usually an IP list) of hosts to +ignore. This makes it possible to ignore IPv6 link-local addresses or loopback +addresses in unreasonable places. + +The lookup may result in a change of name. For compatibility with the dns +lookup, return this via fully_qualified_name as well as updating the host item. +The lookup may also yield more than one IP address, in which case chain on +subsequent host_item structures. + +Arguments: + host a host item with the name and MX filled in; + the address is to be filled in; + multiple IP addresses cause other host items to be + chained on. + ignore_target_hosts a list of hosts to ignore + flags HOST_FIND_QUALIFY_SINGLE ) passed to + HOST_FIND_SEARCH_PARENTS ) dns_init() + fully_qualified_name if not NULL, set to point to host name for + compatibility with host_find_bydns + local_host_check TRUE if a check for the local host is wanted + +Returns: HOST_FIND_FAILED Failed to find the host or domain + HOST_FIND_AGAIN Try again later + HOST_FOUND Host found - data filled in + HOST_FOUND_LOCAL Host found and is the local host +*/ + +int +host_find_byname(host_item *host, const uschar *ignore_target_hosts, int flags, + const uschar **fully_qualified_name, BOOL local_host_check) +{ +int yield, times; +host_item *last = NULL; +BOOL temp_error = FALSE; +int af; + +#ifndef DISABLE_TLS +/* Copy the host name at this point to the value which is used for +TLS certificate name checking, before anything modifies it. */ + +host->certname = host->name; +#endif + +/* Make sure DNS options are set as required. This appears to be necessary in +some circumstances when the get..byname() function actually calls the DNS. */ + +dns_init((flags & HOST_FIND_QUALIFY_SINGLE) != 0, + (flags & HOST_FIND_SEARCH_PARENTS) != 0, + FALSE); /* Cannot retrieve dnssec status so do not request */ + +/* 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 + #ifdef STAND_ALONE + if (disable_ipv6) + #else + if ( disable_ipv6 + || dns_ipv4_lookup + && match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK) + #endif + + { af = AF_INET; times = 1; } + else + { af = AF_INET6; times = 2; } + +/* No IPv6 support */ + +#else /* HAVE_IPV6 */ + af = AF_INET; times = 1; +#endif /* HAVE_IPV6 */ + +/* Initialize the flag that gets set for DNS syntax check errors, so that the +interface to this function can be similar to host_find_bydns. */ + +f.host_find_failed_syntax = FALSE; + +/* Loop to look up both kinds of address in an IPv6 world */ + +for (int i = 1; i <= times; + #if HAVE_IPV6 + af = AF_INET, /* If 2 passes, IPv4 on the second */ + #endif + i++) + { + BOOL ipv4_addr; + int error_num = 0; + struct hostent *hostdata; + unsigned long time_msec = 0; /* compiler quietening */ + + #ifdef STAND_ALONE + printf("Looking up: %s\n", host->name); + #endif + + if (slow_lookup_log) time_msec = get_time_in_ms(); + + #if HAVE_IPV6 + if (f.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 /* not HAVE_IPV6 */ + if (f.running_in_test_harness) + hostdata = host_fake_gethostbyname(host->name, af, &error_num); + else + { + hostdata = gethostbyname(CS host->name); + error_num = h_errno; + } + #endif /* HAVE_IPV6 */ + + if ( slow_lookup_log + && (time_msec = get_time_in_ms() - time_msec) > slow_lookup_log) + log_long_lookup(US"gethostbyname", host->name, time_msec); + + if (!hostdata) + { + uschar * error; + switch (error_num) + { + case HOST_NOT_FOUND: error = US"HOST_NOT_FOUND"; break; + case TRY_AGAIN: error = US"TRY_AGAIN"; temp_error = TRUE; break; + case NO_RECOVERY: error = US"NO_RECOVERY"; temp_error = TRUE; break; + case NO_DATA: error = US"NO_DATA"; break; + #if NO_DATA != NO_ADDRESS + case NO_ADDRESS: error = US"NO_ADDRESS"; break; + #endif + default: error = US"?"; break; + } + + DEBUG(D_host_lookup) debug_printf("%s(af=%s) returned %d (%s)\n", + f.running_in_test_harness ? "host_fake_gethostbyname" : +#if HAVE_IPV6 +# if HAVE_GETIPNODEBYNAME + "getipnodebyname", +# else + "gethostbyname2", +# endif +#else + "gethostbyname", +#endif + af == AF_INET ? "inet" : "inet6", error_num, error); + + continue; + } + if (!(hostdata->h_addr_list)[0]) continue; + + /* Replace the name with the fully qualified one if necessary, and fill in + the fully_qualified_name pointer. */ + + if (hostdata->h_name[0] && Ustrcmp(host->name, hostdata->h_name) != 0) + host->name = string_copy_dnsdomain(US hostdata->h_name); + if (fully_qualified_name) *fully_qualified_name = host->name; + + /* Get the list of addresses. IPv4 and IPv6 addresses can be distinguished + by their different lengths. Scan the list, ignoring any that are to be + ignored, and build a chain from the rest. */ + + ipv4_addr = hostdata->h_length == sizeof(struct in_addr); + + for (uschar ** addrlist = USS hostdata->h_addr_list; *addrlist; addrlist++) + { + uschar *text_address = + host_ntoa(ipv4_addr? AF_INET:AF_INET6, *addrlist, NULL, NULL); + + #ifndef STAND_ALONE + if ( ignore_target_hosts + && verify_check_this_host(&ignore_target_hosts, NULL, host->name, + text_address, NULL) == OK) + { + DEBUG(D_host_lookup) + debug_printf("ignored host %s [%s]\n", host->name, text_address); + continue; + } + #endif + + /* If this is the first address, last is NULL and we put the data in the + original block. */ + + if (!last) + { + host->address = text_address; + host->port = PORT_NONE; + host->status = hstatus_unknown; + host->why = hwhy_unknown; + host->dnssec = DS_UNK; + last = host; + } + + /* Else add further host item blocks for any other addresses, keeping + the order. */ + + else + { + host_item *next = store_get(sizeof(host_item), GET_UNTAINTED); + next->name = host->name; +#ifndef DISABLE_TLS + next->certname = host->certname; +#endif + next->mx = host->mx; + next->address = text_address; + next->port = PORT_NONE; + next->status = hstatus_unknown; + next->why = hwhy_unknown; + next->dnssec = DS_UNK; + next->last_try = 0; + next->next = last->next; + last->next = next; + last = next; + } + } + } + +/* If no hosts were found, the address field in the original host block will be +NULL. If temp_error is set, at least one of the lookups gave a temporary error, +so we pass that back. */ + +if (!host->address) + { + uschar *msg = + #ifndef STAND_ALONE + !message_id[0] && smtp_in + ? string_sprintf("no IP address found for host %s (during %s)", host->name, + smtp_get_connection_info()) : + #endif + string_sprintf("no IP address found for host %s", host->name); + + HDEBUG(D_host_lookup) debug_printf("%s\n", msg); + if (temp_error) goto RETURN_AGAIN; + if (host_checking || !f.log_testing_mode) + log_write(L_host_lookup_failed, LOG_MAIN, "%s", msg); + return HOST_FIND_FAILED; + } + +/* Remove any duplicate IP addresses, then check to see if this is the local +host if required. */ + +host_remove_duplicates(host, &last); +yield = local_host_check? + host_scan_for_local_hosts(host, &last, NULL) : HOST_FOUND; + +HDEBUG(D_host_lookup) + { + if (fully_qualified_name) + debug_printf("fully qualified name = %s\n", *fully_qualified_name); + debug_printf("%s looked up these IP addresses:\n", + #if HAVE_IPV6 + #if HAVE_GETIPNODEBYNAME + "getipnodebyname" + #else + "gethostbyname2" + #endif + #else + "gethostbyname" + #endif + ); + for (const host_item * h = host; h != last->next; h = h->next) + debug_printf(" name=%s address=%s\n", h->name, + h->address ? h->address : US"<null>"); + } + +/* Return the found status. */ + +return yield; + +/* Handle the case when there is a temporary error. If the name matches +dns_again_means_nonexist, return permanent rather than temporary failure. */ + +RETURN_AGAIN: + { +#ifndef STAND_ALONE + int rc; + const uschar *save = deliver_domain; + deliver_domain = host->name; /* set $domain */ + rc = match_isinlist(host->name, CUSS &dns_again_means_nonexist, 0, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL); + deliver_domain = save; + if (rc == OK) + { + DEBUG(D_host_lookup) debug_printf("%s is in dns_again_means_nonexist: " + "returning HOST_FIND_FAILED\n", host->name); + return HOST_FIND_FAILED; + } +#endif + return HOST_FIND_AGAIN; + } +} + + + +/************************************************* +* Fill in a host address from the DNS * +*************************************************/ + +/* Given a host item, with its name, port and mx fields set, and its address +field set to NULL, fill in its IP address from the DNS. If it is multi-homed, +create additional host items for the additional addresses, copying all the +other fields, and randomizing the order. + +On IPv6 systems, AAAA records are sought first, then A records. + +The host name may be changed if the DNS returns a different name - e.g. fully +qualified or changed via CNAME. If fully_qualified_name is not NULL, dns_lookup +ensures that it points to the fully qualified name. However, this is the fully +qualified version of the original name; if a CNAME is involved, the actual +canonical host name may be different again, and so we get it directly from the +relevant RR. Note that we do NOT change the mx field of the host item in this +function as it may be called to set the addresses of hosts taken from MX +records. + +Arguments: + host points to the host item we're filling in + lastptr points to pointer to last host item in a chain of + host items (may be updated if host is last and gets + extended because multihomed) + ignore_target_hosts list of hosts to ignore + allow_ip if TRUE, recognize an IP address and return it + fully_qualified_name if not NULL, return fully qualified name here if + the contents are different (i.e. it must be preset + to something) + dnssec_request if TRUE request the AD bit + dnssec_require if TRUE require the AD bit + whichrrs select ipv4, ipv6 results + +Returns: HOST_FIND_FAILED couldn't find A record + HOST_FIND_AGAIN try again later + HOST_FIND_SECURITY dnssec required but not acheived + HOST_FOUND found AAAA and/or A record(s) + HOST_IGNORED found, but all IPs ignored +*/ + +static int +set_address_from_dns(host_item *host, host_item **lastptr, + const uschar *ignore_target_hosts, BOOL allow_ip, + const uschar **fully_qualified_name, + BOOL dnssec_request, BOOL dnssec_require, int whichrrs) +{ +host_item *thishostlast = NULL; /* Indicates not yet filled in anything */ +BOOL v6_find_again = FALSE; +BOOL dnssec_fail = FALSE; +int i; +dns_answer * dnsa; + +#ifndef DISABLE_TLS +/* Copy the host name at this point to the value which is used for +TLS certificate name checking, before any CNAME-following modifies it. */ + +host->certname = host->name; +#endif + +/* If allow_ip is set, a name which is an IP address returns that value +as its address. This is used for MX records when allow_mx_to_ip is set, for +those sites that feel they have to flaunt the RFC rules. */ + +if (allow_ip && string_is_ip_address(host->name, NULL) != 0) + { + #ifndef STAND_ALONE + if ( ignore_target_hosts + && verify_check_this_host(&ignore_target_hosts, NULL, host->name, + host->name, NULL) == OK) + return HOST_IGNORED; + #endif + + host->address = host->name; + return HOST_FOUND; + } + +dnsa = store_get_dns_answer(); + +/* On an IPv6 system, unless IPv6 is disabled, go round the loop up to twice, +looking for AAAA records the first time. However, unless doing standalone +testing, we force an IPv4 lookup if the domain matches dns_ipv4_lookup global. +On an IPv4 system, go round the loop once only, looking only for A records. */ + +#if HAVE_IPV6 + #ifndef STAND_ALONE + if ( disable_ipv6 + || !(whichrrs & HOST_FIND_BY_AAAA) + || dns_ipv4_lookup + && match_isinlist(host->name, CUSS &dns_ipv4_lookup, 0, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK + ) + i = 0; /* look up A records only */ + else + #endif /* STAND_ALONE */ + + i = 1; /* look up AAAA and A records */ + +/* The IPv4 world */ + +#else /* HAVE_IPV6 */ + i = 0; /* look up A records only */ +#endif /* HAVE_IPV6 */ + +for (; i >= 0; i--) + { + static int types[] = { T_A, T_AAAA }; + int type = types[i]; + int randoffset = i == (whichrrs & HOST_FIND_IPV4_FIRST ? 1 : 0) + ? 500 : 0; /* Ensures v6/4 sort order */ + dns_scan dnss; + + int rc = dns_lookup_timerwrap(dnsa, host->name, type, fully_qualified_name); + lookup_dnssec_authenticated = !dnssec_request ? NULL + : dns_is_secure(dnsa) ? US"yes" : US"no"; + + DEBUG(D_dns) + if ( (dnssec_request || dnssec_require) + && !dns_is_secure(dnsa) + && dns_is_aa(dnsa) + ) + debug_printf("DNS lookup of %.256s (A/AAAA) requested AD, but got AA\n", host->name); + + /* We want to return HOST_FIND_AGAIN if one of the A or AAAA lookups + fails or times out, but not if another one succeeds. (In the early + IPv6 days there are name servers that always fail on AAAA, but are happy + to give out an A record. We want to proceed with that A record.) */ + + if (rc != DNS_SUCCEED) + { + if (i == 0) /* Just tried for an A record, i.e. end of loop */ + { + if (host->address != NULL) + i = HOST_FOUND; /* AAAA was found */ + else if (rc == DNS_AGAIN || rc == DNS_FAIL || v6_find_again) + i = HOST_FIND_AGAIN; + else + i = HOST_FIND_FAILED; /* DNS_NOMATCH or DNS_NODATA */ + goto out; + } + + /* Tried for an AAAA record: remember if this was a temporary + error, and look for the next record type. */ + + if (rc != DNS_NOMATCH && rc != DNS_NODATA) v6_find_again = TRUE; + continue; + } + + if (dnssec_request) + { + if (dns_is_secure(dnsa)) + { + DEBUG(D_host_lookup) debug_printf("%s A DNSSEC\n", host->name); + if (host->dnssec == DS_UNK) /* set in host_find_bydns() */ + host->dnssec = DS_YES; + } + else + { + if (dnssec_require) + { + dnssec_fail = TRUE; + DEBUG(D_host_lookup) debug_printf("dnssec fail on %s for %.256s", + i>0 ? "AAAA" : "A", host->name); + continue; + } + if (host->dnssec == DS_YES) /* set in host_find_bydns() */ + { + DEBUG(D_host_lookup) debug_printf("%s A cancel DNSSEC\n", host->name); + host->dnssec = DS_NO; + lookup_dnssec_authenticated = US"no"; + } + } + } + + /* Lookup succeeded: fill in the given host item with the first non-ignored + address found; create additional items for any others. A single A6 record + may generate more than one address. The lookup had a chance to update the + fqdn; we do not want any later times round the loop to do so. */ + + fully_qualified_name = NULL; + + for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); + rr; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == type) + { + dns_address * da = dns_address_from_rr(dnsa, rr); + + DEBUG(D_host_lookup) + if (!da) debug_printf("no addresses extracted from A6 RR for %s\n", + host->name); + + /* This loop runs only once for A and AAAA records, but may run + several times for an A6 record that generated multiple addresses. */ + + for (; da; da = da->next) + { + #ifndef STAND_ALONE + if (ignore_target_hosts != NULL && + verify_check_this_host(&ignore_target_hosts, NULL, + host->name, da->address, NULL) == OK) + { + DEBUG(D_host_lookup) + debug_printf("ignored host %s [%s]\n", host->name, da->address); + continue; + } + #endif + + /* If this is the first address, stick it in the given host block, + and change the name if the returned RR has a different name. */ + + if (thishostlast == NULL) + { + if (strcmpic(host->name, rr->name) != 0) + host->name = string_copy_dnsdomain(rr->name); + host->address = da->address; + host->sort_key = host->mx * 1000 + random_number(500) + randoffset; + host->status = hstatus_unknown; + host->why = hwhy_unknown; + thishostlast = host; + } + + /* Not the first address. Check for, and ignore, duplicates. Then + insert in the chain at a random point. */ + + else + { + int new_sort_key; + host_item *next; + + /* End of our local chain is specified by "thishostlast". */ + + for (next = host;; next = next->next) + { + if (Ustrcmp(CS da->address, next->address) == 0) break; + if (next == thishostlast) { next = NULL; break; } + } + if (next != NULL) continue; /* With loop for next address */ + + /* Not a duplicate */ + + new_sort_key = host->mx * 1000 + random_number(500) + randoffset; + next = store_get(sizeof(host_item), GET_UNTAINTED); + + /* New address goes first: insert the new block after the first one + (so as not to disturb the original pointer) but put the new address + in the original block. */ + + if (new_sort_key < host->sort_key) + { + *next = *host; /* Copies port */ + host->next = next; + host->address = da->address; + host->sort_key = new_sort_key; + if (thishostlast == host) thishostlast = next; /* Local last */ + if (*lastptr == host) *lastptr = next; /* Global last */ + } + + /* Otherwise scan down the addresses for this host to find the + one to insert after. */ + + else + { + host_item *h = host; + while (h != thishostlast) + { + if (new_sort_key < h->next->sort_key) break; + h = h->next; + } + *next = *h; /* Copies port */ + h->next = next; + next->address = da->address; + next->sort_key = new_sort_key; + if (h == thishostlast) thishostlast = next; /* Local last */ + if (h == *lastptr) *lastptr = next; /* Global last */ + } + } + } + } + } + +/* Control gets here only if the second lookup (the A record) succeeded. +However, the address may not be filled in if it was ignored. */ + +i = host->address + ? HOST_FOUND + : dnssec_fail + ? HOST_FIND_SECURITY + : HOST_IGNORED; + +out: + store_free_dns_answer(dnsa); + return i; +} + + + + +/************************************************* +* Find IP addresses and host names via DNS * +*************************************************/ + +/* The input is a host_item structure with the name field filled in and the +address field set to NULL. This may be in a chain of other host items. The +lookup may result in more than one IP address, in which case we must created +new host blocks for the additional addresses, and insert them into the chain. +The original name may not be fully qualified. Use the fully_qualified_name +argument to return the official name, as returned by the resolver. + +Arguments: + host point to initial host item + ignore_target_hosts a list of hosts to ignore + whichrrs flags indicating which RRs to look for: + HOST_FIND_BY_SRV => look for SRV + HOST_FIND_BY_MX => look for MX + HOST_FIND_BY_A => look for A + HOST_FIND_BY_AAAA => look for AAAA + also flags indicating how the lookup is done + HOST_FIND_QUALIFY_SINGLE ) passed to the + HOST_FIND_SEARCH_PARENTS ) resolver + HOST_FIND_IPV4_FIRST => reverse usual result ordering + HOST_FIND_IPV4_ONLY => MX results elide ipv6 + srv_service when SRV used, the service name + srv_fail_domains DNS errors for these domains => assume nonexist + mx_fail_domains DNS errors for these domains => assume nonexist + dnssec_d.request => make dnssec request: domainlist + dnssec_d.require => ditto and nonexist failures + fully_qualified_name if not NULL, return fully-qualified name + removed set TRUE if local host was removed from the list + +Returns: HOST_FIND_FAILED Failed to find the host or domain; + if there was a syntax error, + host_find_failed_syntax is set. + HOST_FIND_AGAIN Could not resolve at this time + HOST_FIND_SECURITY dnsssec required but not acheived + HOST_FOUND Host found + HOST_FOUND_LOCAL The lowest MX record points to this + machine, if MX records were found, or + an A record that was found contains + an address of the local host +*/ + +int +host_find_bydns(host_item *host, const uschar *ignore_target_hosts, int whichrrs, + uschar *srv_service, uschar *srv_fail_domains, uschar *mx_fail_domains, + const dnssec_domains *dnssec_d, + const uschar **fully_qualified_name, BOOL *removed) +{ +host_item *h, *last; +int rc = DNS_FAIL; +int ind_type = 0; +int yield; +dns_answer * dnsa = store_get_dns_answer(); +dns_scan dnss; +BOOL dnssec_require = dnssec_d + && match_isinlist(host->name, CUSS &dnssec_d->require, + 0, &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK; +BOOL dnssec_request = dnssec_require + || ( dnssec_d + && match_isinlist(host->name, CUSS &dnssec_d->request, + 0, &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) == OK); +dnssec_status_t dnssec; + +/* Set the default fully qualified name to the incoming name, initialize the +resolver if necessary, set up the relevant options, and initialize the flag +that gets set for DNS syntax check errors. */ + +if (fully_qualified_name != NULL) *fully_qualified_name = host->name; +dns_init((whichrrs & HOST_FIND_QUALIFY_SINGLE) != 0, + (whichrrs & HOST_FIND_SEARCH_PARENTS) != 0, + dnssec_request); +f.host_find_failed_syntax = FALSE; + +/* First, if requested, look for SRV records. The service name is given; we +assume TCP protocol. DNS domain names are constrained to a maximum of 256 +characters, so the code below should be safe. */ + +if (whichrrs & HOST_FIND_BY_SRV) + { + gstring * g; + uschar * temp_fully_qualified_name; + int prefix_length; + + g = string_fmt_append(NULL, "_%s._tcp.%n%.256s", + srv_service, &prefix_length, host->name); + temp_fully_qualified_name = string_from_gstring(g); + ind_type = T_SRV; + + /* Search for SRV records. If the fully qualified name is different to + the input name, pass back the new original domain, without the prepended + magic. */ + + dnssec = DS_UNK; + lookup_dnssec_authenticated = NULL; + rc = dns_lookup_timerwrap(dnsa, temp_fully_qualified_name, ind_type, + CUSS &temp_fully_qualified_name); + + DEBUG(D_dns) + if ((dnssec_request || dnssec_require) + && !dns_is_secure(dnsa) + && dns_is_aa(dnsa)) + debug_printf("DNS lookup of %.256s (SRV) requested AD, but got AA\n", host->name); + + if (dnssec_request) + { + if (dns_is_secure(dnsa)) + { dnssec = DS_YES; lookup_dnssec_authenticated = US"yes"; } + else + { dnssec = DS_NO; lookup_dnssec_authenticated = US"no"; } + } + + if (temp_fully_qualified_name != g->s && fully_qualified_name != NULL) + *fully_qualified_name = temp_fully_qualified_name + prefix_length; + + /* On DNS failures, we give the "try again" error unless the domain is + listed as one for which we continue. */ + + if (rc == DNS_SUCCEED && dnssec_require && !dns_is_secure(dnsa)) + { + log_write(L_host_lookup_failed, LOG_MAIN, + "dnssec fail on SRV for %.256s", host->name); + rc = DNS_FAIL; + } + if (rc == DNS_FAIL || rc == DNS_AGAIN) + { +#ifndef STAND_ALONE + if (match_isinlist(host->name, CUSS &srv_fail_domains, 0, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) != OK) +#endif + { yield = HOST_FIND_AGAIN; goto out; } + DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA " + "(domain in srv_fail_domains)\n", rc == DNS_FAIL ? "FAIL":"AGAIN"); + } + } + +/* If we did not find any SRV records, search the DNS for MX records, if +requested to do so. If the result is DNS_NOMATCH, it means there is no such +domain, and there's no point in going on to look for address records with the +same domain. The result will be DNS_NODATA if the domain exists but has no MX +records. On DNS failures, we give the "try again" error unless the domain is +listed as one for which we continue. */ + +if (rc != DNS_SUCCEED && whichrrs & HOST_FIND_BY_MX) + { + ind_type = T_MX; + dnssec = DS_UNK; + lookup_dnssec_authenticated = NULL; + rc = dns_lookup_timerwrap(dnsa, host->name, ind_type, fully_qualified_name); + + DEBUG(D_dns) + if ( (dnssec_request || dnssec_require) + && !dns_is_secure(dnsa) + && dns_is_aa(dnsa)) + debug_printf("DNS lookup of %.256s (MX) requested AD, but got AA\n", host->name); + + if (dnssec_request) + if (dns_is_secure(dnsa)) + { + DEBUG(D_host_lookup) debug_printf("%s (MX resp) DNSSEC\n", host->name); + dnssec = DS_YES; lookup_dnssec_authenticated = US"yes"; + } + else + { + dnssec = DS_NO; lookup_dnssec_authenticated = US"no"; + } + + switch (rc) + { + case DNS_NOMATCH: + yield = HOST_FIND_FAILED; goto out; + + case DNS_SUCCEED: + if (!dnssec_require || dns_is_secure(dnsa)) + break; + DEBUG(D_host_lookup) + debug_printf("dnssec fail on MX for %.256s", host->name); +#ifndef STAND_ALONE + if (match_isinlist(host->name, CUSS &mx_fail_domains, 0, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) != OK) + { yield = HOST_FIND_SECURITY; goto out; } +#endif + rc = DNS_FAIL; + /*FALLTHROUGH*/ + + case DNS_FAIL: + case DNS_AGAIN: +#ifndef STAND_ALONE + if (match_isinlist(host->name, CUSS &mx_fail_domains, 0, + &domainlist_anchor, NULL, MCL_DOMAIN, TRUE, NULL) != OK) +#endif + { yield = HOST_FIND_AGAIN; goto out; } + DEBUG(D_host_lookup) debug_printf("DNS_%s treated as DNS_NODATA " + "(domain in mx_fail_domains)\n", (rc == DNS_FAIL)? "FAIL":"AGAIN"); + break; + } + } + +/* If we haven't found anything yet, and we are requested to do so, try for an +A or AAAA record. If we find it (or them) check to see that it isn't the local +host. */ + +if (rc != DNS_SUCCEED) + { + if (!(whichrrs & (HOST_FIND_BY_A | HOST_FIND_BY_AAAA))) + { + DEBUG(D_host_lookup) debug_printf("Address records are not being sought\n"); + yield = HOST_FIND_FAILED; + goto out; + } + + last = host; /* End of local chainlet */ + host->mx = MX_NONE; + host->port = PORT_NONE; + host->dnssec = DS_UNK; + lookup_dnssec_authenticated = NULL; + rc = set_address_from_dns(host, &last, ignore_target_hosts, FALSE, + fully_qualified_name, dnssec_request, dnssec_require, whichrrs); + + /* If one or more address records have been found, check that none of them + are local. Since we know the host items all have their IP addresses + inserted, host_scan_for_local_hosts() can only return HOST_FOUND or + HOST_FOUND_LOCAL. We do not need to scan for duplicate IP addresses here, + because set_address_from_dns() removes them. */ + + if (rc == HOST_FOUND) + rc = host_scan_for_local_hosts(host, &last, removed); + else + if (rc == HOST_IGNORED) rc = HOST_FIND_FAILED; /* No special action */ + + DEBUG(D_host_lookup) + if (host->address) + { + if (fully_qualified_name) + debug_printf("fully qualified name = %s\n", *fully_qualified_name); + for (host_item * h = host; h != last->next; h = h->next) + debug_printf("%s %s mx=%d sort=%d %s\n", h->name, + h->address ? h->address : US"<null>", h->mx, h->sort_key, + h->status >= hstatus_unusable ? US"*" : US""); + } + + yield = rc; + goto out; + } + +/* We have found one or more MX or SRV records. Sort them according to +precedence. Put the data for the first one into the existing host block, and +insert new host_item blocks into the chain for the remainder. For equal +precedences one is supposed to randomize the order. To make this happen, the +sorting is actually done on the MX value * 1000 + a random number. This is put +into a host field called sort_key. + +In the case of hosts with both IPv6 and IPv4 addresses, we want to choose the +IPv6 address in preference. At this stage, we don't know what kind of address +the host has. We choose a random number < 500; if later we find an A record +first, we add 500 to the random number. Then for any other address records, we +use random numbers in the range 0-499 for AAAA records and 500-999 for A +records. + +At this point we remove any duplicates that point to the same host, retaining +only the one with the lowest precedence. We cannot yet check for precedence +greater than that of the local host, because that test cannot be properly done +until the addresses have been found - an MX record may point to a name for this +host which is not the primary hostname. */ + +last = NULL; /* Indicates that not even the first item is filled yet */ + +for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); + rr; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) if (rr->type == ind_type) + { + int precedence, weight; + int port = PORT_NONE; + const uschar * s = rr->data; /* MUST be unsigned for GETSHORT */ + uschar data[256]; + + GETSHORT(precedence, s); /* Pointer s is advanced */ + + /* For MX records, we use a random "weight" which causes multiple records of + the same precedence to sort randomly. */ + + if (ind_type == T_MX) + weight = random_number(500); + else + { + /* SRV records are specified with a port and a weight. The weight is used + in a special algorithm. However, to start with, we just use it to order the + records of equal priority (precedence). */ + GETSHORT(weight, s); + GETSHORT(port, s); + } + + /* Get the name of the host pointed to. */ + + (void)dn_expand(dnsa->answer, dnsa->answer + dnsa->answerlen, s, + (DN_EXPAND_ARG4_TYPE)data, sizeof(data)); + + /* Check that we haven't already got this host on the chain; if we have, + keep only the lower precedence. This situation shouldn't occur, but you + never know what junk might get into the DNS (and this case has been seen on + more than one occasion). */ + + if (last) /* This is not the first record */ + { + host_item *prev = NULL; + + for (h = host; h != last->next; prev = h, h = h->next) + if (strcmpic(h->name, data) == 0) + { + DEBUG(D_host_lookup) + debug_printf("discarded duplicate host %s (MX=%d)\n", data, + precedence > h->mx ? precedence : h->mx); + if (precedence >= h->mx) goto NEXT_MX_RR; /* Skip greater precedence */ + if (h == host) /* Override first item */ + { + h->mx = precedence; + host->sort_key = precedence * 1000 + weight; + goto NEXT_MX_RR; + } + + /* Unwanted host item is not the first in the chain, so we can get + get rid of it by cutting it out. */ + + prev->next = h->next; + if (h == last) last = prev; + break; + } + } + + /* If this is the first MX or SRV record, put the data into the existing host + block. Otherwise, add a new block in the correct place; if it has to be + before the first block, copy the first block's data to a new second block. */ + + if (!last) + { + host->name = string_copy_dnsdomain(data); + host->address = NULL; + host->port = port; + host->mx = precedence; + host->sort_key = precedence * 1000 + weight; + host->status = hstatus_unknown; + host->why = hwhy_unknown; + host->dnssec = dnssec; + last = host; + } + else + + /* Make a new host item and seek the correct insertion place */ + { + int sort_key = precedence * 1000 + weight; + host_item * next = store_get(sizeof(host_item), GET_UNTAINTED); + next->name = string_copy_dnsdomain(data); + next->address = NULL; + next->port = port; + next->mx = precedence; + next->sort_key = sort_key; + next->status = hstatus_unknown; + next->why = hwhy_unknown; + next->dnssec = dnssec; + next->last_try = 0; + + /* Handle the case when we have to insert before the first item. */ + + if (sort_key < host->sort_key) + { + host_item htemp; + htemp = *host; + *host = *next; + *next = htemp; + host->next = next; + if (last == host) last = next; + } + else + + /* Else scan down the items we have inserted as part of this exercise; + don't go further. */ + { + for (h = host; h != last; h = h->next) + if (sort_key < h->next->sort_key) + { + next->next = h->next; + h->next = next; + break; + } + + /* Join on after the last host item that's part of this + processing if we haven't stopped sooner. */ + + if (h == last) + { + next->next = last->next; + last->next = next; + last = next; + } + } + } + + NEXT_MX_RR: continue; + } + +if (!last) /* No rr of correct type; give up */ + { + yield = HOST_FIND_FAILED; + goto out; + } + +/* If the list of hosts was obtained from SRV records, there are two things to +do. First, if there is only one host, and it's name is ".", it means there is +no SMTP service at this domain. Otherwise, we have to sort the hosts of equal +priority according to their weights, using an algorithm that is defined in RFC +2782. The hosts are currently sorted by priority and weight. For each priority +group we have to pick off one host and put it first, and then repeat for any +remaining in the same priority group. */ + +if (ind_type == T_SRV) + { + host_item ** pptr; + + if (host == last && host->name[0] == 0) + { + DEBUG(D_host_lookup) debug_printf("the single SRV record is \".\"\n"); + yield = HOST_FIND_FAILED; + goto out; + } + + DEBUG(D_host_lookup) + { + debug_printf("original ordering of hosts from SRV records:\n"); + for (h = host; h != last->next; h = h->next) + debug_printf(" %s P=%d W=%d\n", h->name, h->mx, h->sort_key % 1000); + } + + for (pptr = &host, h = host; h != last; pptr = &h->next, h = h->next) + { + int sum = 0; + host_item *hh; + + /* Find the last following host that has the same precedence. At the same + time, compute the sum of the weights and the running totals. These can be + stored in the sort_key field. */ + + for (hh = h; hh != last; hh = hh->next) + { + int weight = hh->sort_key % 1000; /* was precedence * 1000 + weight */ + sum += weight; + hh->sort_key = sum; + if (hh->mx != hh->next->mx) break; + } + + /* If there's more than one host at this precedence (priority), we need to + pick one to go first. */ + + if (hh != h) + { + host_item *hhh; + host_item **ppptr; + int randomizer = random_number(sum + 1); + + for (ppptr = pptr, hhh = h; + hhh != hh; + ppptr = &hhh->next, hhh = hhh->next) + if (hhh->sort_key >= randomizer) + break; + + /* hhh now points to the host that should go first; ppptr points to the + place that points to it. Unfortunately, if the start of the minilist is + the start of the entire list, we can't just swap the items over, because + we must not change the value of host, since it is passed in from outside. + One day, this could perhaps be changed. + + The special case is fudged by putting the new item *second* in the chain, + and then transferring the data between the first and second items. We + can't just swap the first and the chosen item, because that would mean + that an item with zero weight might no longer be first. */ + + if (hhh != h) + { + *ppptr = hhh->next; /* Cuts it out of the chain */ + + if (h == host) + { + host_item temp = *h; + *h = *hhh; + *hhh = temp; + hhh->next = temp.next; + h->next = hhh; + } + else + { + hhh->next = h; /* The rest of the chain follows it */ + *pptr = hhh; /* It takes the place of h */ + h = hhh; /* It's now the start of this minilist */ + } + } + } + + /* A host has been chosen to be first at this priority and h now points + to this host. There may be others at the same priority, or others at a + different priority. Before we leave this host, we need to put back a sort + key of the traditional MX kind, in case this host is multihomed, because + the sort key is used for ordering the multiple IP addresses. We do not need + to ensure that these new sort keys actually reflect the order of the hosts, + however. */ + + h->sort_key = h->mx * 1000 + random_number(500); + } /* Move on to the next host */ + } + +/* Now we have to find IP addresses for all the hosts. We have ensured above +that the names in all the host items are unique. Before release 4.61 we used to +process records from the additional section in the DNS packet that returned the +MX or SRV records. However, a DNS name server is free to drop any resource +records from the additional section. In theory, this has always been a +potential problem, but it is exacerbated by the advent of IPv6. If a host had +several IPv4 addresses and some were not in the additional section, at least +Exim would try the others. However, if a host had both IPv4 and IPv6 addresses +and all the IPv4 (say) addresses were absent, Exim would try only for a IPv6 +connection, and never try an IPv4 address. When there was only IPv4 +connectivity, this was a disaster that did in practice occur. + +So, from release 4.61 onwards, we always search for A and AAAA records +explicitly. The names shouldn't point to CNAMES, but we use the general lookup +function that handles them, just in case. If any lookup gives a soft error, +change the default yield. + +For these DNS lookups, we must disable qualify_single and search_parents; +otherwise invalid host names obtained from MX or SRV records can cause trouble +if they happen to match something local. */ + +yield = HOST_FIND_FAILED; /* Default yield */ +dns_init(FALSE, FALSE, /* Disable qualify_single and search_parents */ + dnssec_request || dnssec_require); + +for (h = host; h != last->next; h = h->next) + { + if (h->address) continue; /* Inserted by a multihomed host */ + + rc = set_address_from_dns(h, &last, ignore_target_hosts, allow_mx_to_ip, + NULL, dnssec_request, dnssec_require, + whichrrs & HOST_FIND_IPV4_ONLY + ? HOST_FIND_BY_A : HOST_FIND_BY_A | HOST_FIND_BY_AAAA); + if (rc != HOST_FOUND) + { + h->status = hstatus_unusable; + switch (rc) + { + case HOST_FIND_AGAIN: yield = rc; h->why = hwhy_deferred; break; + case HOST_FIND_SECURITY: yield = rc; h->why = hwhy_insecure; break; + case HOST_IGNORED: h->why = hwhy_ignored; break; + default: h->why = hwhy_failed; break; + } + } + } + +/* Scan the list for any hosts that are marked unusable because they have +been explicitly ignored, and remove them from the list, as if they did not +exist. If we end up with just a single, ignored host, flatten its fields as if +nothing was found. */ + +if (ignore_target_hosts) + { + host_item *prev = NULL; + for (h = host; h != last->next; h = h->next) + { + REDO: + if (h->why != hwhy_ignored) /* Non ignored host, just continue */ + prev = h; + else if (prev == NULL) /* First host is ignored */ + { + if (h != last) /* First is not last */ + { + if (h->next == last) last = h; /* Overwrite it with next */ + *h = *(h->next); /* and reprocess it. */ + goto REDO; /* C should have redo, like Perl */ + } + } + else /* Ignored host is not first - */ + { /* cut it out */ + prev->next = h->next; + if (h == last) last = prev; + } + } + + if (host->why == hwhy_ignored) host->address = NULL; + } + +/* There is still one complication in the case of IPv6. Although the code above +arranges that IPv6 addresses take precedence over IPv4 addresses for multihomed +hosts, it doesn't do this for addresses that apply to different hosts with the +same MX precedence, because the sorting on MX precedence happens first. So we +have to make another pass to check for this case. We ensure that, within a +single MX preference value, IPv6 addresses come first. This can separate the +addresses of a multihomed host, but that should not matter. */ + +#if HAVE_IPV6 +if (h != last && !disable_ipv6) for (h = host; h != last; h = h->next) + { + host_item temp; + host_item *next = h->next; + + if ( h->mx != next->mx /* If next is different MX */ + || !h->address /* OR this one is unset */ + ) + continue; /* move on to next */ + + if ( whichrrs & HOST_FIND_IPV4_FIRST + ? !Ustrchr(h->address, ':') /* OR this one is IPv4 */ + || next->address + && Ustrchr(next->address, ':') /* OR next is IPv6 */ + + : Ustrchr(h->address, ':') /* OR this one is IPv6 */ + || next->address + && !Ustrchr(next->address, ':') /* OR next is IPv4 */ + ) + continue; /* move on to next */ + + temp = *h; /* otherwise, swap */ + temp.next = next->next; + *h = *next; + h->next = next; + *next = temp; + } +#endif + +/* Remove any duplicate IP addresses and then scan the list of hosts for any +whose IP addresses are on the local host. If any are found, all hosts with the +same or higher MX values are removed. However, if the local host has the lowest +numbered MX, then HOST_FOUND_LOCAL is returned. Otherwise, if at least one host +with an IP address is on the list, HOST_FOUND is returned. Otherwise, +HOST_FIND_FAILED is returned, but in this case do not update the yield, as it +might have been set to HOST_FIND_AGAIN just above here. If not, it will already +be HOST_FIND_FAILED. */ + +host_remove_duplicates(host, &last); +rc = host_scan_for_local_hosts(host, &last, removed); +if (rc != HOST_FIND_FAILED) yield = rc; + +DEBUG(D_host_lookup) + { + if (fully_qualified_name) + debug_printf("fully qualified name = %s\n", *fully_qualified_name); + debug_printf("host_find_bydns yield = %s (%d); returned hosts:\n", + yield == HOST_FOUND ? "HOST_FOUND" : + yield == HOST_FOUND_LOCAL ? "HOST_FOUND_LOCAL" : + yield == HOST_FIND_SECURITY ? "HOST_FIND_SECURITY" : + yield == HOST_FIND_AGAIN ? "HOST_FIND_AGAIN" : + yield == HOST_FIND_FAILED ? "HOST_FIND_FAILED" : "?", + yield); + for (h = host; h != last->next; h = h->next) + { + debug_printf(" %s %s MX=%d %s", h->name, + !h->address ? US"<null>" : h->address, h->mx, + h->dnssec == DS_YES ? US"DNSSEC " : US""); + if (h->port != PORT_NONE) debug_printf("port=%d ", h->port); + if (h->status >= hstatus_unusable) debug_printf("*"); + debug_printf("\n"); + } + } + +out: + +dns_init(FALSE, FALSE, FALSE); /* clear the dnssec bit for getaddrbyname */ +store_free_dns_answer(dnsa); +return yield; +} + + + + +#ifdef SUPPORT_DANE +/* Lookup TLSA record for host/port. +Return: OK success with dnssec; DANE mode + DEFER Do not use this host now, may retry later + FAIL_FORCED No TLSA record; DANE not usable + FAIL Do not use this connection +*/ + +int +tlsa_lookup(const host_item * host, dns_answer * dnsa, BOOL dane_required) +{ +uschar buffer[300]; +const uschar * fullname = buffer; +int rc; +BOOL sec; + +/* TLSA lookup string */ +(void)sprintf(CS buffer, "_%d._tcp.%.256s", host->port, host->name); + +rc = dns_lookup_timerwrap(dnsa, buffer, T_TLSA, &fullname); +sec = dns_is_secure(dnsa); +DEBUG(D_transport) + debug_printf("TLSA lookup ret %s %sDNSSEC\n", dns_rc_names[rc], sec ? "" : "not "); + +switch (rc) + { + case DNS_AGAIN: + return DEFER; /* just defer this TLS'd conn */ + + case DNS_SUCCEED: + if (sec) + { + DEBUG(D_transport) + { + dns_scan dnss; + for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)) + if (rr->type == T_TLSA && rr->size > 3) + { + uint16_t payload_length = rr->size - 3; + uschar s[MAX_TLSA_EXPANDED_SIZE], * sp = s, * p = US rr->data; + + sp += sprintf(CS sp, "%d ", *p++); /* usage */ + sp += sprintf(CS sp, "%d ", *p++); /* selector */ + sp += sprintf(CS sp, "%d ", *p++); /* matchtype */ + while (payload_length-- > 0 && sp-s < (MAX_TLSA_EXPANDED_SIZE - 4)) + sp += sprintf(CS sp, "%02x", *p++); + + debug_printf(" %s\n", s); + } + } + return OK; + } + log_write(0, LOG_MAIN, + "DANE error: TLSA lookup for %s not DNSSEC", host->name); + /*FALLTRHOUGH*/ + + case DNS_NODATA: /* no TLSA RR for this lookup */ + case DNS_NOMATCH: /* no records at all for this lookup */ + return dane_required ? FAIL : FAIL_FORCED; + + default: + case DNS_FAIL: + return dane_required ? FAIL : DEFER; + } +} +#endif /*SUPPORT_DANE*/ + + + +/************************************************* +************************************************** +* Stand-alone test program * +************************************************** +*************************************************/ + +#ifdef STAND_ALONE + +int main(int argc, char **cargv) +{ +host_item h; +int whichrrs = HOST_FIND_BY_MX | HOST_FIND_BY_A | HOST_FIND_BY_AAAA; +BOOL byname = FALSE; +BOOL qualify_single = TRUE; +BOOL search_parents = FALSE; +BOOL request_dnssec = FALSE; +BOOL require_dnssec = FALSE; +uschar **argv = USS cargv; +uschar buffer[256]; + +disable_ipv6 = FALSE; +primary_hostname = US""; +store_init(); +store_pool = POOL_MAIN; +debug_selector = D_host_lookup|D_interface; +debug_file = stdout; +debug_fd = fileno(debug_file); + +printf("Exim stand-alone host functions test\n"); + +host_find_interfaces(); +debug_selector = D_host_lookup | D_dns; + +if (argc > 1) primary_hostname = argv[1]; + +/* So that debug level changes can be done first */ + +dns_init(qualify_single, search_parents, FALSE); + +printf("Testing host lookup\n"); +printf("> "); +while (Ufgets(buffer, 256, stdin) != NULL) + { + int rc; + int len = Ustrlen(buffer); + uschar *fully_qualified_name; + + while (len > 0 && isspace(buffer[len-1])) len--; + buffer[len] = 0; + + if (Ustrcmp(buffer, "q") == 0) break; + + if (Ustrcmp(buffer, "byname") == 0) byname = TRUE; + else if (Ustrcmp(buffer, "no_byname") == 0) byname = FALSE; + else if (Ustrcmp(buffer, "a_only") == 0) whichrrs = HOST_FIND_BY_A | HOST_FIND_BY_AAAA; + else if (Ustrcmp(buffer, "mx_only") == 0) whichrrs = HOST_FIND_BY_MX; + else if (Ustrcmp(buffer, "srv_only") == 0) whichrrs = HOST_FIND_BY_SRV; + else if (Ustrcmp(buffer, "srv+a") == 0) + whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_A | HOST_FIND_BY_AAAA; + else if (Ustrcmp(buffer, "srv+mx") == 0) + whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX; + else if (Ustrcmp(buffer, "srv+mx+a") == 0) + whichrrs = HOST_FIND_BY_SRV | HOST_FIND_BY_MX | HOST_FIND_BY_A | HOST_FIND_BY_AAAA; + else if (Ustrcmp(buffer, "qualify_single") == 0) qualify_single = TRUE; + 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, "request_dnssec") == 0) request_dnssec = TRUE; + else if (Ustrcmp(buffer, "no_request_dnssec") == 0) request_dnssec = FALSE; + else if (Ustrcmp(buffer, "require_dnssec") == 0) require_dnssec = TRUE; + else if (Ustrcmp(buffer, "no_require_dnssec") == 0) require_dnssec = FALSE; + else if (Ustrcmp(buffer, "test_harness") == 0) + f.running_in_test_harness = !f.running_in_test_harness; + else if (Ustrcmp(buffer, "ipv6") == 0) disable_ipv6 = !disable_ipv6; + 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); + _res.retrans = dns_retrans; + } + else if (Ustrncmp(buffer, "retry", 5) == 0) + { + (void)sscanf(CS(buffer+6), "%d", &dns_retry); + _res.retry = dns_retry; + } + else + { + int flags = whichrrs; + dnssec_domains d; + + h.name = buffer; + h.next = NULL; + h.mx = MX_NONE; + h.port = PORT_NONE; + h.status = hstatus_unknown; + h.why = hwhy_unknown; + h.address = NULL; + + if (qualify_single) flags |= HOST_FIND_QUALIFY_SINGLE; + if (search_parents) flags |= HOST_FIND_SEARCH_PARENTS; + + d.request = request_dnssec ? &h.name : NULL; + d.require = require_dnssec ? &h.name : NULL; + + rc = byname + ? host_find_byname(&h, NULL, flags, &fully_qualified_name, TRUE) + : host_find_bydns(&h, NULL, flags, US"smtp", NULL, NULL, + &d, &fully_qualified_name, NULL); + + switch (rc) + { + case HOST_FIND_FAILED: printf("Failed\n"); break; + case HOST_FIND_AGAIN: printf("Again\n"); break; + case HOST_FIND_SECURITY: printf("Security\n"); break; + case HOST_FOUND_LOCAL: printf("Local\n"); break; + } + } + + printf("\n> "); + } + +printf("Testing host_aton\n"); +printf("> "); +while (Ufgets(buffer, 256, stdin) != NULL) + { + int x[4]; + int len = Ustrlen(buffer); + + while (len > 0 && isspace(buffer[len-1])) len--; + buffer[len] = 0; + + if (Ustrcmp(buffer, "q") == 0) break; + + len = host_aton(buffer, x); + printf("length = %d ", len); + for (int i = 0; i < len; i++) + { + printf("%04x ", (x[i] >> 16) & 0xffff); + printf("%04x ", x[i] & 0xffff); + } + printf("\n> "); + } + +printf("\n"); + +printf("Testing host_name_lookup\n"); +printf("> "); +while (Ufgets(buffer, 256, stdin) != NULL) + { + int len = Ustrlen(buffer); + while (len > 0 && isspace(buffer[len-1])) len--; + buffer[len] = 0; + if (Ustrcmp(buffer, "q") == 0) break; + sender_host_address = buffer; + sender_host_name = NULL; + sender_host_aliases = NULL; + host_lookup_msg = US""; + host_lookup_failed = FALSE; + if (host_name_lookup() == FAIL) /* Debug causes printing */ + printf("Lookup failed:%s\n", host_lookup_msg); + printf("\n> "); + } + +printf("\n"); + +return 0; +} +#endif /* STAND_ALONE */ + +/* vi: aw ai sw=2 +*/ +/* End of host.c */ |