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