-/* $Cambridge: exim/src/src/host.c,v 1.15 2005/09/19 10:13:39 ph10 Exp $ */
+/* $Cambridge: exim/src/src/host.c,v 1.21 2006/02/07 16:36:25 ph10 Exp $ */
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2006 */
/* See the file NOTICE for conditions of use and distribution. */
/* Functions for finding hosts, either by gethostbyname(), gethostbyaddr(), or
static struct hostent *
host_fake_gethostbyname(uschar *name, int af, int *error_num)
{
-int ipa;
+#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;
(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;
- BOOL no_helo = FALSE;
-
- /* Comparing a HELO name to a host name is easy */
-
- if (sender_helo_name == NULL ||
- strcmpic(sender_host_name, sender_helo_name) == 0)
- no_helo = TRUE;
-
- /* If HELO/EHLO was followed by an IP literal, it's much more 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, an IPv6 address
- may not be given in canonical form, so we have to canonicize it 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] == ']')
- {
- uschar *helo_ip;
- int offset = 1;
-
- if (strncmpic(sender_helo_name+1, US"IPv6:",5) == 0) offset += 5;
- helo_ip = string_copyn(sender_helo_name + offset, len - offset - 1);
+ if (show_helo && strcmpic(sender_host_name, sender_helo_name) == 0)
+ show_helo = FALSE;
- if (string_is_ip_address(helo_ip, NULL) != 0)
- {
- int x[4];
- int size;
- size = host_aton(helo_ip, x);
- helo_ip = store_get(48); /* large enough for full IPv6 */
- (void)host_nmtoa(size, x, -1, helo_ip, ':');
- if (strcmpic(helo_ip, sender_host_address) == 0) no_helo = TRUE;
- }
- }
-
- if (no_helo)
- {
- 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)
{
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 ipv;
int port = host_address_extract_port(s); /* Leaves just the IP address */
- if (string_is_ip_address(s, NULL) == 0)
+ 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 */
* Fill in a host address from the DNS *
*************************************************/
-/* Given a host item, with its name 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.
+/* 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, A6 records are sought first (but only if support for A6 is
configured - they may never become mainstream), then AAAA records are sought,
#endif
host->address = host->name;
- host->port = PORT_NONE;
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 (strcmpic(host->name, rr->name) != 0)
host->name = string_copy_dnsdomain(rr->name);
host->address = da->address;
- host->port = PORT_NONE;
host->sort_key = host->mx * 1000 + random_number(500) + randoffset;
host->status = hstatus_unknown;
host->why = hwhy_unknown;
if (new_sort_key < host->sort_key)
{
- *next = *host;
+ *next = *host; /* Copies port */
host->next = next;
host->address = da->address;
- host->port = PORT_NONE;
host->sort_key = new_sort_key;
if (thishostlast == host) thishostlast = next; /* Local last */
if (*lastptr == host) *lastptr = next; /* Global last */
if (new_sort_key < h->next->sort_key) break;
h = h->next;
}
- *next = *h;
+ *next = *h; /* Copies port */
h->next = next;
next->address = da->address;
- next->port = PORT_NONE;
next->sort_key = new_sort_key;
if (h == thishostlast) thishostlast = next; /* Local last */
if (h == *lastptr) *lastptr = next; /* Global last */
/*************************************************
-* Find IP addresses and names for host via DNS *
+* Find IP addresses and host names via DNS *
*************************************************/
-/* The input is a host_item structure with the name 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.
+/* 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
{
int precedence;
int weight = 0; /* For SRV records */
- int port = PORT_NONE; /* For SRV records */
+ int port = PORT_NONE;
uschar *s; /* MUST be unsigned for GETSHORT */
uschar data[256];
} /* Move on to the next host */
}
-/* Now we have to ensure addresses exist for all the hosts. We have ensured
-above that the names in the host items are all unique. The addresses may have
-been returned in the additional data section of the DNS query. Because it is
-more expensive to scan the returned DNS records (because you have to expand the
-names) we do a single scan over them, and multiple scans of the chain of host
-items (which is typically only 3 or 4 long anyway.) Add extra host items for
-multi-homed hosts. */
-
-for (rr = dns_next_rr(&dnsa, &dnss, RESET_ADDITIONAL);
- rr != NULL;
- rr = dns_next_rr(&dnsa, &dnss, RESET_NEXT))
- {
- dns_address *da;
- int status = hstatus_unknown;
- int why = hwhy_unknown;
- int randoffset;
-
- if (rr->type != T_A
- #if HAVE_IPV6
- && rr->type != T_AAAA
- #ifdef SUPPORT_A6
- && rr->type != T_A6
- #endif
- #endif
- ) continue;
-
- /* Find the first host that matches this record's name. If there isn't
- one, move on to the next RR. */
-
- for (h = host; h != last->next; h = h->next)
- { if (strcmpic(h->name, rr->name) == 0) break; }
- if (h == last->next) continue;
-
- /* For IPv4 addresses, add 500 to the random part of the sort key, to ensure
- they sort after IPv6 addresses. */
-
- randoffset = (rr->type == T_A)? 500 : 0;
-
- /* Get the list of textual addresses for this RR. There may be more than one
- if it is an A6 RR. Then loop to handle multiple addresses from an A6 record.
- If there are none, nothing will get done - the record is ignored. */
-
- for (da = dns_address_from_rr(&dnsa, rr); da != NULL; da = da->next)
- {
- /* Set status for an ignorable host. */
-
- #ifndef STAND_ALONE
- if (ignore_target_hosts != NULL &&
- verify_check_this_host(&ignore_target_hosts, NULL, h->name,
- da->address, NULL) == OK)
- {
- DEBUG(D_host_lookup)
- debug_printf("ignored host %s [%s]\n", h->name, da->address);
- status = hstatus_unusable;
- why = hwhy_ignored;
- }
- #endif
-
- /* If the address is already set for this host, it may be that
- we just have a duplicate DNS record. Alternatively, this may be
- a multi-homed host. Search all items with the same host name
- (they will all be together) and if this address is found, skip
- to the next RR. */
-
- if (h->address != NULL)
- {
- int new_sort_key;
- host_item *thishostlast;
- host_item *hh = h;
-
- do
- {
- if (hh->address != NULL && Ustrcmp(CS da->address, hh->address) == 0)
- goto DNS_NEXT_RR; /* Need goto to escape from inner loop */
- thishostlast = hh;
- hh = hh->next;
- }
- while (hh != last->next && strcmpic(hh->name, rr->name) == 0);
-
- /* We have a multi-homed host, since we have a new address for
- an existing name. Create a copy of the current item, and give it
- the new address. RRs can be in arbitrary order, but one is supposed
- to randomize the addresses of multi-homed hosts, so compute a new
- sorting key and do that. [Latest SMTP RFC says not to randomize multi-
- homed hosts, but to rely on the resolver. I'm not happy about that -
- caching in the resolver will not rotate as often as the name server
- does.] */
-
- new_sort_key = h->mx * 1000 + random_number(500) + randoffset;
- hh = store_get(sizeof(host_item));
-
- /* 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 < h->sort_key)
- {
- *hh = *h; /* Note: copies the port */
- h->next = hh;
- h->address = da->address;
- h->sort_key = new_sort_key;
- h->status = status;
- h->why = why;
- }
-
- /* Otherwise scan down the addresses for this host to find the
- one to insert after. */
-
- else
- {
- while (h != thishostlast)
- {
- if (new_sort_key < h->next->sort_key) break;
- h = h->next;
- }
- *hh = *h; /* Note: copies the port */
- h->next = hh;
- hh->address = da->address;
- hh->sort_key = new_sort_key;
- hh->status = status;
- hh->why = why;
- }
-
- if (h == last) last = hh; /* Inserted after last */
- }
-
- /* The existing item doesn't have its address set yet, so just set it.
- Ensure that an IPv4 address gets its sort key incremented in case an IPv6
- address is found later. */
-
- else
- {
- h->address = da->address; /* Port should be set already */
- h->status = status;
- h->why = why;
- h->sort_key += randoffset;
- }
- } /* Loop for addresses extracted from one RR */
-
- /* Carry on to the next RR. It would be nice to be able to be able to stop
- when every host on the list has an address, but we can't be sure there won't
- be an additional address for a multi-homed host further down the list, so
- we have to continue to the end. */
-
- DNS_NEXT_RR: continue;
- }
-
-/* Set the default yield to failure */
-
-yield = HOST_FIND_FAILED;
-
-/* If we haven't found all the addresses in the additional section, we
-need to search for A or 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.
+/* 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. */
-dns_init(FALSE, FALSE);
+yield = HOST_FIND_FAILED; /* Default yield */
+dns_init(FALSE, FALSE); /* Disable qualify_single and search_parents */
for (h = host; h != last->next; h = h->next)
{
- if (h->address != NULL || h->status == hstatus_unusable) continue;
+ if (h->address != NULL) continue; /* Inserted by a multihomed host */
rc = set_address_from_dns(h, &last, ignore_target_hosts, allow_mx_to_ip, NULL);
if (rc != HOST_FOUND)
{