
/*************************************************
*     Exim - an Internet mail transport agent    *
*************************************************/

/* Copyright (c) University of Cambridge 1995 - 1999 */
/* See the file NOTICE for conditions of use and distribution. */

/* Functions for finding hosts, either by gethostbyname, gethostbyaddr, or via
the DNS. Also contains a function for finding all the running interfaces of
the current host - overridable by the setting of local_interfaces. Also
contains a random number function, used for randomizing hosts with equal MXs
but available for use in other parts of Exim, and various other functions
concerned with hosts and addresses. */


#include "exim.h"

/* While IPv6 is yet young: the definition of T_AAAA may not be included in
arpa/nameser.h. Fudge it here. */

#if HAVE_IPV6
#ifndef T_AAAA
#define T_AAAA 28
#endif
#endif

/* Ancient systems (e.g. SunOS4) don't appear to have T_TXT defined in their
header files. */

#ifndef T_TXT
#define T_TXT 16
#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.

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)
{
static unsigned int seed = 0;
if (seed == 0)
  {
  int p = (int)getpid();
  seed = (int)time(NULL) ^ ((p << 16) | p);
  }
seed = 1103515245 * seed + 12345;
return (unsigned int)(seed >> 16) % limit;
}



/*************************************************
*       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.

Arguments:
  anchor      anchor for the chain
  list        text list

Returns:      nothing
*/

void
host_build_hostlist(host_item **anchor, char *list)
{
char *name;
char buffer[1024];

if (list == NULL) return;

for (name = string_nextinlist(&list, ':', buffer, sizeof(buffer));
     name != NULL;
     name = string_nextinlist(&list, ':', buffer, sizeof(buffer)))
  {
  host_item *h = store_get(sizeof(host_item));
  h->next = NULL;
  h->name = string_copy(name);
  h->address = NULL;
  h->mx = -1;
  h->status = hstatus_unknown;
  h->why = hwhy_unknown;
  h->last_try = 0;
  *anchor = h;
  anchor = &(h->next);
  }
}






#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]"
(d) The two are identical: "host_name [ip address]"
(e) The two are different: "host_name (helo_name) [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 copied into malloc store.

Arguments:  none
Returns:    nothing
*/

void
host_build_sender_fullhost(void)
{
char *fullhost, *rcvhost;

if (sender_host_address == NULL) return;

if (sender_fullhost != NULL) store_free(sender_fullhost);

if (sender_host_name == NULL)
  {
  if (sender_helo_name == NULL)
    {
    fullhost = string_sprintf("[%s]", sender_host_address);
    rcvhost = (sender_ident == NULL)?
      string_sprintf("[%s]", sender_host_address) :
      string_sprintf("[%s] (ident=%s)", sender_host_address, sender_ident);
    }
  else
    {
    fullhost = string_sprintf("(%s) [%s]", sender_helo_name,
      sender_host_address);

    rcvhost = (sender_ident == NULL)?
      string_sprintf("[%s] (helo=%s)", sender_host_address, sender_helo_name) :
      string_sprintf("[%s] (helo=%s ident=%s)", sender_host_address,
        sender_helo_name, sender_ident);
    }
  }
else
  {
  int len;
  if (sender_helo_name == NULL ||
      strcmpic(sender_host_name, sender_helo_name) == 0 ||
        (sender_helo_name[0] == '[' &&
         sender_helo_name[(len=(int)strlen(sender_helo_name))-1] == ']' &&
         strncmpic(sender_helo_name+1, sender_host_address, len - 2) == 0))
    {
    fullhost = string_sprintf("%s [%s]", sender_host_name,
      sender_host_address);
    rcvhost = (sender_ident == NULL)?
      string_sprintf("%s ([%s])", sender_host_name, sender_host_address) :
      string_sprintf("%s ([%s] ident=%s)", sender_host_name,
        sender_host_address, sender_ident);
    }
  else
    {
    fullhost = string_sprintf("%s (%s) [%s]", sender_host_name,
      sender_helo_name, sender_host_address);
    rcvhost = (sender_ident == NULL)?
      string_sprintf("%s ([%s] helo=%s)", sender_host_name,
        sender_host_address, sender_helo_name) :
      string_sprintf("%s\n\t([%s] helo=%s ident=%s)", sender_host_name,
        sender_host_address, sender_helo_name, sender_ident);
    }
  }

sender_fullhost =
  store_malloc((int)strlen(fullhost) + (int)strlen(rcvhost) + 2);
sender_rcvhost = sender_fullhost + (int)strlen(fullhost) + 1;
strcpy(sender_fullhost, fullhost);
strcpy(sender_rcvhost, rcvhost);

DEBUG(5) debug_printf("sender_fullhost = %s\n", sender_fullhost);
DEBUG(5) debug_printf("sender_rcvhost = %s\n", sender_rcvhost);
}

#endif   /* STAND_ALONE */




/*************************************************
*        Decode actions for self reference       *
*************************************************/

/* This function is called from a number of routers on receiving
HOST_FOUND_LOCAL when looking up a supposedly remote host. There are
two cases:

(1)  If the current address is source-routed, then we can remove the
     first source-routing domain and try again with the remainder by
     returning ISLOCAL. This will cause the new address to be routed
     ab initio.

(2)  For a non-source-routed address, the action is controlled by a
     generic configuration option called "self" on each router, which
     can be one of:

     . freeze:                       Log the incident, freeze, and return ERROR

     . defer:                        Log the incident and return DEFER

     . send:                         Carry on with the delivery regardless -
                                     this makes sense only if the SMTP
                                     listener on this machine is a differently
                                     configured MTA

     . fail_soft                     The router just fails, and the address
                                     gets passed to the next router.

     . reroute:<new-domain>          Change the domain to the given domain
                                     and return ISLOCAL so it gets passed back
                                     to the directors or routers.

     . reroute:rewrite:<new-domain>  The same, but headers containing the
                                     old domain get rewritten.

     . local                         Set addr->forced_local and return ISLOCAL,
                                     so the address gets passed to the
                                     directors.

Arguments:
  addr     the address being routed
  host     the host that is local, with MX set (or -1 if MX not used)
  code     the action to be taken (one of the self_xxx enums) for non-
             source-routed addresses
  rewrite  TRUE if rewriting headers required for ISLOCAL
  new      new domain to be used for ISLOCAL

Returns:   (1) ISLOCAL for a source-routed address, or
           (2) ERROR, DEFER, ISLOCAL, FAIL, FORCEFAIL, or OK, according
               to the value of code.
*/

int
host_self_action(address_item *addr, host_item *host, int code, BOOL rewrite,
  char *new)
{
char *format, *msg;

/* Handle a source-routed address */

if (addr->local_part[0] == ',' || addr->local_part[0] == ':')
  {
  char *p = addr->local_part + 1;

  if (addr->local_part[0] == ':')
    {
    while (*p != '@' && *p != 0) p++;
    if (*p != 0)
      addr->domain = addr->route_domain = string_copy(p+1);
    addr->local_part =
      string_copyn(addr->local_part + 1, p - addr->local_part - 1);
    }
  else
    {
    while (*p != ',' && *p != ':') p++;
    addr->domain = addr->route_domain =
      string_copyn(addr->local_part + 2, p - addr->local_part - 2);
    addr->local_part = p;
    }

  DEBUG(2) debug_printf("source-routed address is local: rewrote\n  "
    "domain = %s\n  local_part = %s\n", addr->route_domain,
      addr->local_part);
  return ISLOCAL;
  }

/* Handle a non-source-routed address */

if (host->mx >= 0)
  {
  format = "lowest MX record for %s points to local host";
  msg = "lowest numbered MX record points to local host";
  }
else
  {
  format = "remote host address for %s is the local host";
  msg = "remote host address is the local host";
  }

switch (code)
  {
  case self_freeze:
  log_write(0, LOG_MAIN, format, addr->route_domain);
  addr->message = msg;
  addr->special_action = SPECIAL_FREEZE;
  return ERROR;

  case self_defer:
  addr->message = msg;
  return DEFER;

  case self_local:
  DEBUG(2)
    {
    debug_printf(format, addr->route_domain);
    debug_printf("\n  address passed to directors: self_host = %s\n",
      host->name);
    }
  addr->forced_local = TRUE;
  addr->self_hostname = string_copy(host->name);
  return ISLOCAL;

  case self_reroute:
  DEBUG(2)
    {
    debug_printf(format, addr->route_domain);
    debug_printf(": domain changed to %s\n", new);
    }
  addr->route_domain = string_copy(new);
  addr->rewrite_headers = rewrite;
  return ISLOCAL;

  case self_send:
  DEBUG(2)
    {
    debug_printf(format, addr->route_domain);
    debug_printf(": configured to try delivery anyway\n");
    }
  return OK;

  case self_fail:
  DEBUG(2)
    {
    debug_printf(format, addr->route_domain);
    debug_printf(": router failed soft\n");
    }
  addr->message = msg;
  return FAIL;

  case self_forcefail:
  DEBUG(2)
    {
    debug_printf(format, addr->route_domain);
    debug_printf(": router failed hard\n");
    }
  addr->message = msg;
  return FORCEFAIL;
  }

return FAIL;   /* paranoia */
}



/*************************************************
*         Find addresses on local interfaces     *
*************************************************/

/* This function finds the addresses of all the running interfaces on the
machine. A chain of blocks containing the textual form of the addresses is
returned.

Problems:

  (1) Solaris 2 has the SIOGIFNUM call to get the number of
  interfaces, but other OS (including Solaris 1) appear not to. So just screw
  in a largeish fixed number. Unfortunately, the www addressing scheme means
  that some hosts have a very large number of virtual interfaces. Such hosts
  are recommended to set local_interfaces to avoid problems with this (see
  host_find_interfaces below).

  (2) If the standard code is run on IRIX, it does not return any alias
  interfaces. There is special purpose code for that operating system, which
  uses the sysctl() function.

Arguments:    none
Returns:      a chain of ip_address_items, each pointing to a textual
              version of an IP address
*/


#ifndef SYSCTL_IP_INTERFACES

/* This is the form of the function that uses ioctl() in a way that works on
most versions of Unix. */

#define MAXINTERFACES 250

static ip_address_item *
host_find_running_interfaces(void)
{
struct ifconf ifc;
struct ifreq ifreq, *ifr;
int vs;
ip_address_item *yield = NULL;
ip_address_item *last = NULL;
ip_address_item  *next;
char *cp;
char buf[MAXINTERFACES*sizeof(struct ifreq)];

/* We have to create a socket in order to do ioctls on it to find out
what we want to know. */

if ((vs = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
  log_write(0, LOG_PANIC_DIE, "Unable to create socket to find interface "
    "addresses: %d %s", errno, strerror(errno));

/* Get the interface configuration. */

ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = buf;

if (ioctl(vs, SIOCGIFCONF, (char *)&ifc) < 0)
  log_write(0, LOG_PANIC_DIE, "Unable to get interface configuration: %d %s",
    errno, strerror(errno));

/* For each interface, check it is an IP interface, get its flags, and see if
it is up; if not, skip.

BSD systems differ from others in what SIOCGIFCONF returns. Other systems
return a vector of ifreq structures whose size is as defined by the structure.
BSD systems allow sockaddrs to be longer than their sizeof, which in turn makes
the ifreq structures longer than their sizeof. The code below has its origins
in amd and ifconfig; it uses the sa_len field of each sockaddr to determine
each item's length. */

for (cp = buf; cp < buf + ifc.ifc_len; cp +=
    #ifdef HAVE_SA_LEN
    mac_max(ifr->ifr_addr.sa_len, sizeof(ifr->ifr_addr)) +
      sizeof(ifr->ifr_name))
    #else
    sizeof(*ifr))
    #endif
  {
  ifr = (struct ifreq *)cp;

  /* If not an IP interface, skip */

  if (ifr->ifr_addr.sa_family != AF_INET
  #if HAVE_IPV6
    && ifr->ifr_addr.sa_family != AF_INET6
  #endif
    ) continue;

  /* Get the interface flags, and if the interface is down, continue */

  ifreq = *ifr;
  if (ioctl(vs, SIOCGIFFLAGS, (char *)&ifreq) < 0)
    log_write(0, LOG_PANIC_DIE, "Unable to get interface %s flags: %d %s",
      ifreq.ifr_name, errno, strerror(errno));
  if ((ifreq.ifr_flags & IFF_UP) == 0) continue;

  /* Get the IP address of the interface */

  if (ioctl(vs, SIOCGIFADDR, (char *)&ifreq) < 0)
    log_write(0, LOG_PANIC_DIE, "Unable to get interface address: %d %s",
      errno, strerror(errno));

  /* Create a data block for the address, fill in the data, and put it on the
  chain. This data has to survive for ever, so use malloc. */

  next = store_malloc(sizeof(ip_address_item));
  next->next = NULL;
  (void)host_ntoa(-1, &ifreq.ifr_addr, next->address);

  if (yield == NULL) yield = last = next; else
    {
    last->next = next;
    last = next;
    }

  DEBUG(9) debug_printf("Actual local interface address is %s\n",
    last->address);
  }

/* Close the socket, and return the chain of data blocks. */

close(vs);
return yield;
}


/* This is the special form of the function using sysctl() which is the only
form that returns all the aliases on IRIX systems. This code has its origins
in a sample program that came from within SGI. */

#else

#include <sys/sysctl.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/soioctl.h>
#include <net/route.h>


#define ROUNDUP(a) ((a) > 0 ? (1 + (((a) - 1) | (sizeof(__uint64_t) -1))) \
                    : sizeof(__uint64_t))
#ifdef _HAVE_SA_LEN
#define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
#else
#define ADVANCE(x, n) (x += ROUNDUP(_FAKE_SA_LEN_DST(n)))
#endif


static ip_address_item *
host_find_running_interfaces(void)
{
ip_address_item *yield = NULL;
ip_address_item *last = NULL;
ip_address_item *next;

size_t needed;
int mib[6];
char *buf, *nextaddr, *lim;
register struct if_msghdr *ifm;

mib[0] = CTL_NET;
mib[1] = PF_ROUTE;
mib[2] = 0;
mib[3] = 0;
mib[4] = NET_RT_IFLIST;
mib[5] = 0;

/* Get an estimate of the amount of store needed, then get the store and
get the data into it. Any error causes a panic death. */

if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0)
  log_write(0, LOG_PANIC_DIE, "iflist-sysctl-estimate failed: %s",
    strerror(errno));

buf = store_get(needed);

if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0)
  log_write(0, LOG_PANIC_DIE, "sysctl of ifnet list failed: %s",
    strerror(errno));

/* Now fish out the data for each interface */

lim  = buf + needed;
for (nextaddr = buf; nextaddr < lim; nextaddr += ifm->ifm_msglen)
  {
  ifm = (struct if_msghdr *)nextaddr;

  if (ifm->ifm_type != RTM_IFINFO)
    {
    struct ifa_msghdr *ifam = (struct ifa_msghdr *)ifm;
    struct sockaddr_in *mask = NULL, *addr = NULL;

    if ((ifam->ifam_addrs & RTA_NETMASK) != 0)
      mask = (struct sockaddr_in *)(ifam + 1);

    if ((ifam->ifam_addrs & RTA_IFA) != 0)
      {
      char *cp = (char *)mask;
      struct sockaddr *sa = (struct sockaddr *)mask;
      ADVANCE(cp, sa);
      addr = (struct sockaddr_in *)cp;
      }

    /* Create a data block for the address, fill in the data, and put it on
    the chain. This data has to survive for ever, so use malloc. */

    if (addr != NULL)
      {
      next = store_malloc(sizeof(ip_address_item));
      next->next = NULL;

      (void)host_ntoa(-1, addr, next->address);

      if (yield == NULL) yield = last = next; else
        {
        last->next = next;
        last = next;
        }

      DEBUG(8) debug_printf("Actual local interface address is %s\n",
        last->address);
      }
    }
  }

return yield;
}

#endif




/*************************************************
*         Find addresses on local interfaces     *
*************************************************/

/* This function finds the addresses of local IP interfaces. If the option
local_interfaces is not set, it calls host_find_running_interfaces() in order
to find all the actual interfaces on the host. Otherwise it takes the list in
local_interfaces. Used to detect when apparently remote hosts are really local.
A chain of blocks containing the textual form of the addresses is returned.

Arguments:    none
Returns:      a chain of ip_address_items, each pointing to a textual
              version of an IP address
*/

ip_address_item *
host_find_interfaces(void)
{
char *s;
char *listptr = local_interfaces;
char buffer[64];
ip_address_item *yield = NULL;
ip_address_item *last = NULL;
ip_address_item *next;

if (local_interfaces == NULL) return host_find_running_interfaces();

for (s = string_nextinlist(&listptr, ':', buffer, sizeof(buffer));
     s != NULL;
     s = string_nextinlist(&listptr, ':', buffer, sizeof(buffer)))
  {
  if (!string_is_ip_address(s))
    log_write(0, LOG_MAIN|LOG_PANIC_DIE, "Malformed IP address \"%s\" in "
      "local_interfaces", s);

  /* Data has to survive for ever, so use malloc. */

  next = store_malloc(sizeof(ip_address_item));
  next->next = NULL;
  strcpy(next->address, s);

  if (yield == NULL) yield = last = next; else
    {
    last->next = next;
    last = next;
    }

  DEBUG(8) debug_printf("Configured local interface address is %s\n",
    last->address);
  }

return yield;
}





/*************************************************
*        Convert 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.

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)
  buffer     if NULL, the result is returned in gotten store;
             else points to a buffer to hold the answer

Returns:     pointer to character string
*/

char *
host_ntoa(int type, const void *arg, char *buffer)
{
char *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 char *, whereas the IPv4
function inet_ntoa() returns just char *, and some picky compilers insist
on warning if one assigns a const char * to a char *. Hence the casts. */

#if HAVE_IPV6
char 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 = (char *)inet_ntop(family, &(sk->sin6_addr), addr_buffer,
      sizeof(addr_buffer));
    }
  else
    {
    struct sockaddr_in *sk = (struct sockaddr_in *)arg;
    yield = (char *)inet_ntop(family, &(sk->sin_addr), addr_buffer,
      sizeof(addr_buffer));
    }
  }
else
  yield = (char *)inet_ntop(type, arg, addr_buffer, sizeof(addr_buffer));
#else

/* The old world */

if (type < 0)
  yield = inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
else
  yield = inet_ntoa(*((struct in_addr *)arg));
#endif

/* If there is no buffer, put the string into some new store. */

if (buffer == NULL) return string_copy(yield);
strcpy(buffer, 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.

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(char *address, int *bin)
{
int x[4];
int v4offset = 0;

/* Handle IPv6 address, which may end with an IPv4 address */

#if HAVE_IPV6
if (strchr(address, ':') != NULL)
  {
  char *p = address;
  char *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. */

  while (*p != 0)
    {
    int len = strcspn(p, ":");
    if (len == 0) nulloffset = ci;
    component[ci++] = p;
    p += len + 1;
    }

  /* 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 (strchr(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--] = "";
    }

  /* 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] = (strtol(component[i], NULL, 16) << 16) +
      strtol(component[i+1], NULL, 16);

  /* If there was no terminating v4 component, we are done. */

  if (!ipv4_ends) return 4;
  }
#endif

/* Handle IPv4 address */

sscanf(address, "%d.%d.%d.%d", x, x+1, x+2, x+3);
bin[v4offset] = (x[0] << 24) + (x[1] << 16) + (x[2] << 8) + x[3];
return v4offset+1;
}




/*************************************************
*       Convert address + mask to binary         *
*************************************************/

/* This function is given the textual form of an address and a mask, separated
by a slash. It converts both to binary and returns them in vectors of ints.
The number of ints used is returned. Both IPv4 and IPv6 addressing modes are
supported.

Arguments:
  s         the input string
  address   points to vector of int for address
  mask      points to vector of int for mask
  error     where to put a message on error

Returns:    number of vector words used, or 0 if there was a problem
*/

int
host_amton(char *s, int *address, int *mask, char **error)
{
int size;
char *slash = strchr(s, '/');

if (slash == NULL)
  {
  *error = "\"/\" missing";
  return 0;
  }

/* Temporarily terminate the string at the slash, and turn the initial
address into binary. */

*slash = 0;
size = host_aton(s, address);
*slash = '/';

/* For IPv4 addresses, the mask may be another IPv4 address, containing dots,
or an integer. The syntax has been checked. */

if (size == 1 && strchr(slash+1, '.') != NULL) host_aton(slash+1, mask);

/* For IPv6 addresses, and optionally for IPv4 addresses, the mask is just
an integer, and we have to make up the bit pattern. */

else
  {
  int i;
  int mlen = atoi(slash+1);
  for (i = 0; i < 4; i++)
    {
    if (mlen >= 32)
      {
      mask[i] = -1;
      mlen -= 32;
      }
    else if (mlen == 0) mask[i] = 0;
    else
      {
      mask[i] = (-1) << (32 - mlen);
      mlen = 0;
      }
    }
  }

/* Return the number of ints used */

return size;
}






/*************************************************
*       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.

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        the first host in the chain
  last        the last host in the chain
  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 *last, BOOL *removed)
{
int yield = HOST_FIND_FAILED;
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 &&
      match_isinlist(h->name, hosts_treat_as_local, &re_hosts_treat_as_local,
      TRUE))
    goto FOUND_LOCAL;
  #endif

  if (h->address != NULL)
    {
    ip_address_item *ip;
    for (ip = local_interface_data; ip != NULL; ip = ip->next)
      if (strcmp(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)
  {
  DEBUG(8) debug_printf((h->mx >= 0)?
    "local host has lowest MX\n" :
    "local host found for non-MX address\n");
  return HOST_FOUND_LOCAL;
  }

DEBUG(8)
  {
  debug_printf("local host in 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 = NULL;
return yield;
}




/*************************************************
*        Find host name given an IP address      *
*************************************************/

/* For the moment, just use gethostbyaddr, and pass back a single string,
either in permanent or pooled store.

Arguments:
  address     an IPv4 address in dotted-quad form OR
              an IPv6 address in colon-separated form
  use_malloc  TRUE if permanent store is required

Returns:      the name of the host or NULL if not found

In addition, the variable host_findbyaddr_msg is set to an empty string on
sucess, or to a reason for the failure otherwise, in a form suitable for
tagging onto an error message. */

char *
host_find_byaddr(char *address, BOOL use_malloc)
{
char *s, *t, *yield;
host_item h, *hh;
int len;
struct hostent *hosts;
struct in_addr addr;

host_findbyaddr_msg = "";

HDEBUG(2) debug_printf("looking up host name for %s\n", address);

/* Lookup on IPv6 system */

#if HAVE_IPV6
if (strchr(address, ':') != NULL)
  {
  struct in6_addr addr6;
  if (inet_pton(AF_INET6, address, &addr6) != 1)
    log_write(0, LOG_PANIC_DIE, "unable to parse \"%s\"", address);
  hosts = gethostbyaddr((char *)(&addr6), sizeof(addr6), AF_INET6);
  }
else
  {
  if (inet_pton(AF_INET, address, &addr) != 1)
    log_write(0, LOG_PANIC_DIE, "unable to parse \"%s\"", address);
  hosts = gethostbyaddr((char *)(&addr), sizeof(addr), AF_INET);
  }

/* Do lookup on IPv4 system */

#else
addr.s_addr = (S_ADDR_TYPE)inet_addr(address);
hosts = gethostbyaddr((char *)(&addr), sizeof(addr), AF_INET);
#endif

/* Failed to look up the host */

if (hosts == NULL)
  {
  HDEBUG(2) debug_printf("IP address lookup failed\n");
  host_findbyaddr_msg = " (failed to find host name from IP address)";
  return NULL;
  }

/* Copy and lowercase the name. We do this before checking the forward lookup
because gethostbyname() and gethostbyaddr() share static storage in some
systems, so we need to take a copy of the name first. */

s = (char *)hosts->h_name;
len = (int)strlen(s) + 1;
t = yield = use_malloc? store_malloc(len) : store_get(len);
while (*s != 0) *t++ = tolower(*s++);
*t = 0;

HDEBUG(2) debug_printf("IP address lookup yielded %s\n", yield);

/* 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. */

h.name = yield;
h.address = NULL;
h.next = NULL;

if (host_find_byname(&h, NULL, FALSE) == HOST_FIND_FAILED)
  {
  HDEBUG(2) debug_printf("no IP address found for %s\n", yield);
  host_findbyaddr_msg = string_sprintf(
    " (no IP address found for %s, the host name obtained from %s)",
    yield, address);
  return NULL;
  }

else for (hh = &h; hh != NULL; hh = hh->next)
  if (strcmp(hh->address, address) == 0) return yield;

HDEBUG(2) debug_printf("%s does not match any IP address for %s\n",
  address, yield);
host_findbyaddr_msg =
  string_sprintf(" (%s does not match any IP address for %s)", address, yield);

return NULL;
}




/*************************************************
*    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(). Of course, gethostbyname() may use
the DNS, but it doesn't do MX processing. If more than one address is given,
chain on additional host items, with other relevant fields copied. The type of
store required for them is set by use_malloc.

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 filled in; the address is
                         to be filled in; multiple IP addresses cause other
                         host items to be chained on.
  fully_qualified_name   if not NULL, set to point to host name for
                         compatibility with host_find_bydns
  use_malloc             TRUE if additional items must be in permanent store

Returns:                 HOST_FIND_FAILED  Failed to find the host or domain
                         HOST_FOUND        Host found
                         HOST_FOUND_LOCAL  Host found and is the local host
*/

int
host_find_byname(host_item *host, char **fully_qualified_name, BOOL use_malloc)
{
int i, yield;
char **addrlist;
host_item *last = NULL;

/* 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. */

#if HAVE_IPV6
int af = AF_INET6;
int times = 2;
#else
int times = 1;
#endif

/* Initialize the flag that gets set for DNS syntax check errors, so that the
interface to this function can be the same as host_find_bydns. */

host_find_failed_syntax = FALSE;

/* Loop to look up both kinds of address in an IPv6 world */

for (i = 1; i <= times; i++)
  {
  BOOL ipv4_addr;

  #if HAVE_IPV6
  struct hostent *hostdata = gethostbyname2(host->name, af);
  af = AF_INET;
  #else
  struct hostent *hostdata = gethostbyname(host->name);
  #endif

  if (hostdata == NULL || (hostdata->h_addr_list)[0] == NULL) continue;

  /* Replace the name with the fully qualified one if necessary, and fill in
  the fully_qualified_name pointer. */

  if (strcmp(host->name, hostdata->h_name) != 0)
    host->name = use_malloc? string_copy_malloc((char *)hostdata->h_name) :
      string_copy((char *)hostdata->h_name);
  if (fully_qualified_name != NULL) *fully_qualified_name = host->name;

  /* Get the list of addresses. IPv4 and IPv6 addresses can be distinguished
  by their different lengths. */

  addrlist = hostdata->h_addr_list;
  ipv4_addr = hostdata->h_length == sizeof(struct in_addr);

  /* If this is the first address, last == NULL and we put the data in the
  original block. */

  if (last == NULL)
    {
    host->address = host_ntoa(ipv4_addr? AF_INET:AF_INET6, *addrlist++, NULL);
    host->mx = -1;
    host->status = hstatus_unknown;
    host->why = hwhy_unknown;
    last = host;
    }

  /* Now add further host item blocks for any other addresses, keeping
  the order. */

  while (*addrlist != NULL)
    {
    host_item *next = use_malloc?
      store_malloc(sizeof(host_item)) : store_get(sizeof(host_item));
    next->name = host->name;
    next->ident_string = host->ident_string;
    next->address = host_ntoa(ipv4_addr? AF_INET:AF_INET6, *addrlist++, NULL);
    next->status = hstatus_unknown;
    next->why = hwhy_unknown;
    next->last_try = 0;
    next->mx = -1;
    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 (host->address == NULL) return HOST_FIND_FAILED;

/* Now check to see if this is the local host. */

yield = host_scan_for_local_hosts(host, last, NULL);

DEBUG(8)
  {
  host_item *h;
  if (fully_qualified_name != NULL)
    debug_printf("fully qualified name = %s\n", *fully_qualified_name);
  for (h = host; h != last->next; h = h->next)
    debug_printf("%s %s %d %s\n", h->name,
      (h->address == NULL)? "<null>" : h->address, h->mx,
      (h->status >= hstatus_unusable)? "*" : "");
  }

/* Return the found status. */

return yield;
}



/*************************************************
*        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.

On IPv6 systems, AAAA records are sought first, and then A records are sought
as well.

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 initial host item
  fully_qualified_name  if not NULL, return fully qualified name here if
                          the contents are different (i.e. it must be preset
                          to something)

Returns:       HOST_FIND_FAILED     couldn't find A record
               HOST_FIND_AGAIN      try again later
               HOST_FOUND           found AAAA and/or A record(s)
*/

static int
set_address_from_dns(host_item *host, char **fully_qualified_name)
{
dns_record *rr;
host_item *last = NULL;
int i, rc, type;

/* On an IPv6 system, go round the loop twice, looking for AAAA records
the first time. */

#if HAVE_IPV6
i = 1;
type = T_AAAA;

/* On an IPv4 system, go round the loop once only, looking for A records */

#else
i = 0;
type = T_A;
#endif

/* Loop for both AAAA and A records on an IPv6 system */

for (; i >= 0; type = T_A, i--)
  {
  rc = dns_lookup(host->name, type, fully_qualified_name);
  if (rc == DNS_AGAIN || rc == DNS_FAIL) return HOST_FIND_AGAIN;
  if (rc == DNS_NOMATCH) continue;

  /* Fill in the given host item with the first address found; create
  additional items for any others. */

  for (rr = dns_next_rr(RESET_ANSWERS); rr != NULL;
       rr = dns_next_rr(RESET_NEXT))
    {
    if (rr->type == type)
      {
      char *address = dns_address_from_rr(rr);

      if (last == NULL)
        {
        if (strcmpic(host->name, rr->name) != 0)
          host->name = string_copy(rr->name);
        host->address = address;
        host->sort_key = host->mx * 100 + random_number(100);
        host->status = hstatus_unknown;
        host->why = hwhy_unknown;
        last = host;
        }
      else
        {
        int new_sort_key = host->mx * 100 + random_number(100);
        host_item *next = 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 < host->sort_key)
          {
          *next = *host;
          host->next = next;
          host->address = address;
          host->sort_key = new_sort_key;
          if (last == host) last = next;
          }

        /* Otherwise scan down the addresses for this host to find the
        one to insert after. */

        else
          {
          host_item *h = host;
          while (h != last)
            {
            if (new_sort_key < h->next->sort_key) break;
            h = h->next;
            }
          *next = *h;
          h->next = next;
          next->address = address;
          next->sort_key = new_sort_key;
          if (h == last) last = next;      /* Inserted after last */
          }
        }
      }
    }
  }

/* If we found at least one A or AAAA record, the host->address field will
have been filled in. */

return (host->address == NULL)? HOST_FIND_FAILED : HOST_FOUND;
}




/*************************************************
*  Find IP addresses and names for host via DNS  *
*************************************************/

/* The input is a host_item structure with the name filled in and the address
field set to NULL. The lookup may result in more than one IP address, in which
case chain on subsequent host_item structures. The names of the hosts are in
general different to the original domain name, but in addition, 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
  mx_only               if TRUE, require MX record(s) to exist
  a_only                if TRUE, don't look for MX records
  qualify_single        if TRUE, ask DNS to qualify single-component names
  search_parents        if TRUE, asd DNS to search parent domains
  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_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, BOOL mx_only, BOOL a_only,
  BOOL qualify_single, BOOL search_parents, char **fully_qualified_name,
  BOOL *removed)
{
host_item *h, *last;
dns_record *rr;
int rc = DNS_FAIL;
int mx_count, mx_addressed, yield;

/* Set the default fully qualified name to the incoming name, initialize the
resolver if necessary, and set up the relevant options. */

if (fully_qualified_name != NULL) *fully_qualified_name = host->name;
dns_init(qualify_single, search_parents);

/* Search the DNS for MX records, possibly via a CNAME. First, initialize the
flag that gets set for DNS syntax check errors. */

host_find_failed_syntax = FALSE;

if (!a_only)
  {
  rc = dns_lookup(host->name, T_MX, fully_qualified_name);

/* Changed to be the same as in set_address_from_dns above */
/*  if (rc == DNS_FAIL) return HOST_FIND_FAILED; */

  if (rc == DNS_FAIL) return HOST_FIND_AGAIN;
  if (rc == DNS_AGAIN) return HOST_FIND_AGAIN;
  }

/* If there were no MX records and mx_only is FALSE, or if a_only is TRUE,
try for an A record, unless there was a syntax error in the name that was used
for the MX lookup. If we find it (or them) check to see that it isn't the local
host. */

if (rc != DNS_SUCCEED)
  {
  if (host_find_failed_syntax) return HOST_FIND_FAILED;

  if (!mx_only || a_only)
    {
    host_item *next = host->next;
    last = host;
    host->mx = -1;
    rc = set_address_from_dns(host, fully_qualified_name);

    /* If one or more A records have been found, find the last one and
    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. */

    if (rc == HOST_FOUND)
      {
      while (last->next != next) last = last->next;
      rc = host_scan_for_local_hosts(host, last, NULL);
      }

    DEBUG(8)
      {
      host_item *h;
      if (host->address != NULL)
        {
        if (fully_qualified_name != NULL)
          debug_printf("fully qualified name = %s\n", *fully_qualified_name);
        for (h = host; h != next; h = h->next)
          debug_printf("%s %s %d %d %s\n", h->name,
            (h->address == NULL)? "<null>" : h->address, h->mx, h->sort_key,
            (h->status >= hstatus_unusable)? "*" : "");
        }
      }

    return rc;
    }

  DEBUG(8) debug_printf("No MX records, and mx_only is TRUE\n");
  return HOST_FIND_FAILED;
  }

/* We have found one or more MX records. Sort them according to precedence, and
create host_item blocks for them. For equal precedences one is supposed to
randomize the order. To make this happen, the sorting is actually done on the
MX value * 100 + a random number. This is put into a host field called
sort_key. 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;
mx_count = mx_addressed = 0;

for (rr = dns_next_rr(RESET_ANSWERS); rr != NULL; rr = dns_next_rr(RESET_NEXT))
  {
  int precedence;
  unsigned char *s;             /* MUST be unsigned for GETSHORT */
  char data[256];

  if (rr->type != T_MX) continue;
  s = rr->data;
  GETSHORT(precedence, s);      /* Pointer s is advanced */

  /* Get the name of the host pointed to. */

  (void)dns_expand(s, data, 256);

  /* 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. */

  if (last != NULL) for (h = host; h != last->next; h = h->next)
    {
    if (strcmpic(h->name, data) == 0)
      {
      if (precedence < h->mx) h->mx = precedence;
      goto NEXT_MX_RR;
      }
    }

  /* Count the number of MX hosts we are setting up. */

  mx_count++;

  /* If this is the first MX record, put the data into the existing host
  block. Otherwise, add a new block in the correct place; it it has to be
  before the first block, copy the first block's data to a new second block. */

  if (last == NULL)
    {
    host->name = string_copy(data);
    host->address = NULL;
    host->mx = precedence;
    host->sort_key = precedence * 100 + random_number(100);
    host->status = hstatus_unknown;
    host->why = hwhy_unknown;
    last = host;
    }

  /* Make a new host item and seek the correct insertion place */

  else
    {
    int sort_key = precedence * 100 + random_number(100);

    host_item *next = store_get(sizeof(host_item));
    next->name = string_copy(data);
    next->address = NULL;
    next->mx = precedence;
    next->sort_key = sort_key;
    next->status = hstatus_unknown;
    next->why = hwhy_unknown;
    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 scan down the items we have inserted as part of this exercise;
    don't go further. */

    else
      {
      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;
  }


/* 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(RESET_ADDITIONAL); rr != NULL;
     rr = dns_next_rr(RESET_NEXT))
  {
  char *address;

  if (rr->type != T_A
  #if HAVE_IPV6
    && rr->type != T_AAAA
  #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;

  /* Set up the textual address for this RR; the function puts it into
  new store. */

  address = dns_address_from_rr(rr);

  /* If the address is already set for this host, it may be that
  we just have a duplicate A-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 && strcmp(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 nameserver
    does.] */

    new_sort_key = h->mx * 100 + random_number(100);
    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;
      h->next = hh;
      h->address = address;
      h->sort_key = new_sort_key;
      }

    /* 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;
      h->next = hh;
      hh->address = address;
      hh->sort_key = new_sort_key;
      }

    if (h == last) last = hh;       /* Inserted after last */
    }

  /* The existing item doesn't have its address set yet. Set it, and
  count the number of existing items we have filled in. */

  else
    {
    h->address = address;
    mx_addressed++;
    }

  /* Carry on to the next RR. It would be nice to be able to stop when
  mx_addressed >= mx_count, but we can't be sure there won't be an additional
  address for a multi-homed host further down the list. */

  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. */

if (mx_addressed < mx_count)
  {
  for (h = host; h != last->next; h = h->next)
    {
    if (h->address != NULL) continue;
    rc = set_address_from_dns(h, NULL);

    /* If the DNS lookup failed, mark this host item unusable and carry
    on. If no addresses are found, we'll fail below. */

    if (rc != HOST_FOUND)
      {
      h->status = hstatus_unusable;
      if (rc == HOST_FIND_AGAIN)
        {
        yield = rc;
        h->why = hwhy_deferred;
        }
      else h->why = hwhy_failed;
      }
    else mx_addressed++;
    }
  }

/* 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. */

rc = host_scan_for_local_hosts(host, last, removed);
if (rc != HOST_FIND_FAILED) yield = rc;

DEBUG(8)
  {
  if (fully_qualified_name != NULL)
    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_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 %d %d %s\n", h->name,
      (h->address == NULL)? "<null>" : h->address, h->mx, h->sort_key,
      (h->status >= hstatus_unusable)? "*" : "");
  }

return yield;
}



/*************************************************
*        Check an IP address against an RBL      *
*************************************************/

/* Anti-spamming "realtime blocking lists" are appearing on the DNS. This
function, given an IP address and the domain of one such list, looks up the
address in the blocking domain.

Arguments:
  address      the string version of the address
  domain       the rbl domain to use
  buffer       somewhere to point an error message

Returns:    DNS_SUCCEED   successful lookup (i.e. the address is blocked)
            DNS_NOMATCH   name not found, or no data found for the given type
            DNS_AGAIN     soft failure, try again later
            DNS_FAIL      DNS failure
*/

int
host_check_rbl(char *address, char *domain, char **bufptr)
{
int bin[4];
int rc;
int type;
char name[256];
char *t = name;

/* In case this is the first time the DNS resolver is being used. */

dns_init(FALSE, FALSE);

/* Handle IPv4 address: if HAVE_IPV6 is false, the result of
host_aton() is always 1. */

if (host_aton(address, bin) == 1)
  {
  int i;
  int x = bin[0];
  for (i = 0; i < 4; i++)
    {
    sprintf(t, "%d.", x & 255);
    while (*t) t++;
    x >>= 8;
    }
  type = T_A;
  }

/* Handle IPv6 address */

#if HAVE_IPV6
else
  {
  int i, j;
  for (j = 3; j >= 0; j--)
    {
    int x = bin[j];
    for (i = 0; i < 8; i++)
      {
      sprintf(t, "%x.", x & 15);
      while (*t) t++;
      x >>= 4;
      }
    }
  type = T_AAAA;
  }
#endif


strcpy(t, domain);
rc = dns_basic_lookup(name, type);

/* If a buffer pointer exists, set it to point to the null string, or to a TXT
record's data if present after success. */

if (rc == DNS_SUCCEED)
  {
  *bufptr = "";
  if (dns_basic_lookup(name, T_TXT) == DNS_SUCCEED)
    {
    dns_record *rr;

    for (rr = dns_next_rr(RESET_ANSWERS); rr != NULL; rr = dns_next_rr(RESET_NEXT))
      if (rr->type == T_TXT) break;

    if (rr != NULL)
      {
      int len = (rr->data)[0];
      if (len > 127) len = 127;
      *bufptr = string_sprintf("%.*s", len, (const char *)(rr->data + 1));
      }

    HDEBUG(8)
      {
      debug_printf("RBL lookup for %s succeeded\n", name);
      debug_printf("=> that means it is black listed at %s\n", domain);
      if (**bufptr != 0) debug_printf("%s\n", *bufptr);
      }
    }
  }

else HDEBUG(8)
  {
  debug_printf("RBL lookup for %s failed\n", name);
  debug_printf("=> that means it's not black listed at %s\n", domain);
  }

return rc;
}




/*************************************************
**************************************************
*             Stand-alone test program           *
**************************************************
*************************************************/

#ifdef STAND_ALONE

/* Note: an address for testing RBL is 192.203.178.39 */
/* Note: an address for testing DUL is 192.203.178.4  */

int main(int argc, char **argv)
{
host_item h;
BOOL byname = FALSE;
BOOL mx_only = FALSE;
BOOL a_only = FALSE;
BOOL qualify_single = TRUE;
BOOL search_parents = FALSE;
char buffer[256];

primary_hostname = "";
debug_level = 9;
debug_file = stdout;

printf("Exim stand-alone host functions test\n");

host_find_running_interfaces();
debug_level = 8;

if (argc > 1) primary_hostname = argv[1];

/* So that debug level changes can be done first */

dns_init(qualify_single, search_parents);

printf("Testing host lookup\n");
printf("> ");
while (fgets(buffer, 256, stdin) != NULL)
  {
  int rc;
  int len = (int)strlen(buffer);
  char *fully_qualified_name;

  while (len > 0 && isspace(buffer[len-1])) len--;
  buffer[len] = 0;

  if (strcmp(buffer, "q") == 0) break;

  if (strcmp(buffer, "byname") == 0) byname = TRUE;
  else if (strcmp(buffer, "no_byname") == 0) byname = FALSE;
  else if (strcmp(buffer, "a_only") == 0) a_only = TRUE;
  else if (strcmp(buffer, "no_a_only") == 0) a_only = FALSE;
  else if (strcmp(buffer, "mx_only") == 0) mx_only = TRUE;
  else if (strcmp(buffer, "no_mx_only") == 0) mx_only = FALSE;
  else if (strcmp(buffer, "qualify_single") == 0) qualify_single = TRUE;
  else if (strcmp(buffer, "no_qualify_single") == 0) qualify_single = FALSE;
  else if (strcmp(buffer, "search_parents") == 0) search_parents = TRUE;
  else if (strcmp(buffer, "no_search_parents") == 0) search_parents = FALSE;
  else if (strncmp(buffer, "retrans", 7) == 0)
    {
    sscanf(buffer+8, "%d", &dns_retrans);
    _res.retrans = dns_retrans;
    }
  else if (strncmp(buffer, "retry", 5) == 0)
    {
    sscanf(buffer+6, "%d", &dns_retry);
    _res.retry = dns_retry;
    }
  else if (isdigit(buffer[0]))
    {
    sscanf(buffer, "%d", &debug_level);
    _res.options &= ~RES_DEBUG;
    DEBUG(11) _res.options |= RES_DEBUG;
    }
  else
    {
    h.name = buffer;
    h.next = NULL;
    h.mx = -1;
    h.status = hstatus_unknown;
    h.why = hwhy_unknown;
    h.address = NULL;

    rc = byname? host_find_byname(&h, &fully_qualified_name, FALSE) :
      host_find_bydns(&h, mx_only, a_only, qualify_single, search_parents,
        &fully_qualified_name, NULL);

    if (rc == HOST_FIND_FAILED) printf("Failed\n");
      else if (rc == HOST_FIND_AGAIN) printf("Again\n");
        else if (rc == HOST_FOUND_LOCAL) printf("Local\n");
    }

  printf("\n> ");
  }

printf("Testing host_aton\n");
printf("> ");
while (fgets(buffer, 256, stdin) != NULL)
  {
  int i;
  int x[4];
  int len = (int)strlen(buffer);

  while (len > 0 && isspace(buffer[len-1])) len--;
  buffer[len] = 0;

  if (strcmp(buffer, "q") == 0) break;

  len = host_aton(buffer, x);
  printf("length = %d ", len);
  for (i = 0; i < len; i++)
    {
    printf("%04x ", (x[i] >> 16) & 0xffff);
    printf("%04x ", x[i] & 0xffff);
    }
  printf("\n> ");
  }

printf("\n");

printf("Testing host_find_byaddr\n");
printf("> ");
while (fgets(buffer, 256, stdin) != NULL)
  {
  char *yield;
  int len = (int)strlen(buffer);
  while (len > 0 && isspace(buffer[len-1])) len--;
  buffer[len] = 0;
  if (strcmp(buffer, "q") == 0) break;
  yield = host_find_byaddr(buffer, FALSE);      /* Debug causes printing */
  if (yield == NULL)
    printf("Lookup failed:%s\n", host_findbyaddr_msg);
  printf("\n> ");
  }

printf("\n");

printf("Testing host_check_rbl\n");
printf("> ");
while (fgets(buffer, 256, stdin) != NULL)
  {
  char *bufptr;
  int len = (int)strlen(buffer);
  while (len > 0 && isspace(buffer[len-1])) len--;
  buffer[len] = 0;
  if (strcmp(buffer, "q") == 0) break;
  (void) host_check_rbl(buffer, "rbl.maps.vix.com", &bufptr);
  printf("\n> ");
  }

printf("\n");
return 0;
}
#endif

/* End of host.c */
