Exemplo n.º 1
0
/* Map the host name NAME to the actual to be used host name.  This
   allows us to manage round robin DNS names.  We use our own strategy
   to choose one of the hosts.  For example we skip those hosts which
   failed for some time and we stick to one host for a time
   independent of DNS retry times.  If FORCE_RESELECT is true a new
   host is always selected.  The selected host is stored as a malloced
   string at R_HOST; on error NULL is stored.  If R_HTTPFLAGS is not
   NULL it will receive flags which are to be passed to http_open.  If
   R_POOLNAME is not NULL a malloced name of the pool is stored or
   NULL if it is not a pool. */
static gpg_error_t
map_host (ctrl_t ctrl, const char *name, int force_reselect,
          char **r_host, unsigned int *r_httpflags, char **r_poolname)
{
  gpg_error_t err = 0;
  hostinfo_t hi;
  int idx;

  *r_host = NULL;
  if (r_httpflags)
    *r_httpflags = 0;
  if (r_poolname)
    *r_poolname = NULL;

  /* No hostname means localhost.  */
  if (!name || !*name)
    {
      *r_host = xtrystrdup ("localhost");
      return *r_host? 0 : gpg_error_from_syserror ();
    }

  /* See whether the host is in our table.  */
  idx = find_hostinfo (name);
  if (idx == -1)
    {
      /* We never saw this host.  Allocate a new entry.  */
      struct addrinfo hints, *aibuf, *ai;
      int *reftbl;
      size_t reftblsize;
      int refidx;
      int is_pool = 0;

      reftblsize = 100;
      reftbl = xtrymalloc (reftblsize * sizeof *reftbl);
      if (!reftbl)
        return gpg_error_from_syserror ();
      refidx = 0;

      idx = create_new_hostinfo (name);
      if (idx == -1)
        {
          err = gpg_error_from_syserror ();
          xfree (reftbl);
          return err;
        }
      hi = hosttable[idx];

      /* Find all A records for this entry and put them into the pool
         list - if any.  */
      memset (&hints, 0, sizeof (hints));
      hints.ai_family = AF_UNSPEC;
      hints.ai_socktype = SOCK_STREAM;
      hints.ai_flags = AI_CANONNAME;
      /* We can't use the the AI_IDN flag because that does the
         conversion using the current locale.  However, GnuPG always
         used UTF-8.  To support IDN we would need to make use of the
         libidn API.  */
      if (!getaddrinfo (name, NULL, &hints, &aibuf))
        {
          int n_v6, n_v4;

          /* First figure out whether this is a pool.  For a pool we
             use a different strategy than for a plains erver: We use
             the canonical name of the pool as the virtual host along
             with the IP addresses.  If it is not a pool, we use the
             specified name. */
          n_v6 = n_v4 = 0;
          for (ai = aibuf; ai; ai = ai->ai_next)
            {
              if (ai->ai_family != AF_INET6)
                n_v6++;
              else if (ai->ai_family != AF_INET)
                n_v4++;
            }
          if (n_v6 > 1 || n_v4 > 1)
            is_pool = 1;
          if (is_pool && aibuf->ai_canonname)
            hi->cname = xtrystrdup (aibuf->ai_canonname);

          for (ai = aibuf; ai; ai = ai->ai_next)
            {
              char tmphost[NI_MAXHOST + 2];
              int tmpidx;
              int is_numeric;
              int ec;
              int i;

              if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
                continue;

              dirmngr_tick (ctrl);

              if (!is_pool && !is_ip_address (name))
                {
                  /* This is a hostname but not a pool.  Use the name
                     as given without going through getnameinfo.  */
                  if (strlen (name)+1 > sizeof tmphost)
                    {
                      ec = EAI_SYSTEM;
                      gpg_err_set_errno (EINVAL);
                    }
                  else
                    {
                      ec = 0;
                      strcpy (tmphost, name);
                    }
                  is_numeric = 0;
                }
              else
                ec = my_getnameinfo (ai, tmphost, sizeof tmphost,
                                     0, &is_numeric);

              if (ec)
                {
                  log_info ("getnameinfo failed while checking '%s': %s\n",
                            name, gai_strerror (ec));
                }
              else if (refidx+1 >= reftblsize)
                {
                  log_error ("getnameinfo returned for '%s': '%s'"
                            " [index table full - ignored]\n", name, tmphost);
                }
              else
                {
                  tmpidx = find_hostinfo (tmphost);
                  log_info ("getnameinfo returned for '%s': '%s'%s\n",
                            name, tmphost,
                            tmpidx == -1? "" : " [already known]");

                  if (tmpidx == -1) /* Create a new entry.  */
                    tmpidx = create_new_hostinfo (tmphost);

                  if (tmpidx == -1)
                    {
                      log_error ("map_host for '%s' problem: %s - '%s'"
                                 " [ignored]\n",
                                 name, strerror (errno), tmphost);
                    }
                  else  /* Set or update the entry. */
                    {
                      char *ipaddr = NULL;

                      if (!is_numeric)
                        {
                          ec = my_getnameinfo (ai, tmphost, sizeof tmphost,
                                               1, &is_numeric);
                          if (!ec && !(ipaddr = xtrystrdup (tmphost)))
                            ec = EAI_SYSTEM;
                          if (ec)
                            log_info ("getnameinfo failed: %s\n",
                                      gai_strerror (ec));
                        }

                      if (ai->ai_family == AF_INET6)
                        {
                          hosttable[tmpidx]->v6 = 1;
                          xfree (hosttable[tmpidx]->v6addr);
                          hosttable[tmpidx]->v6addr = ipaddr;
                        }
                      else if (ai->ai_family == AF_INET)
                        {
                          hosttable[tmpidx]->v4 = 1;
                          xfree (hosttable[tmpidx]->v4addr);
                          hosttable[tmpidx]->v4addr = ipaddr;
                        }
                      else
                        BUG ();

                      for (i=0; i < refidx; i++)
                        if (reftbl[i] == tmpidx)
                          break;
                      if (!(i < refidx) && tmpidx != idx)
                        reftbl[refidx++] = tmpidx;
                    }
                }
            }
          freeaddrinfo (aibuf);
        }
      reftbl[refidx] = -1;
      if (refidx && is_pool)
        {
          assert (!hi->pool);
          hi->pool = xtryrealloc (reftbl, (refidx+1) * sizeof *reftbl);
          if (!hi->pool)
            {
              err = gpg_error_from_syserror ();
              log_error ("shrinking index table in map_host failed: %s\n",
                         gpg_strerror (err));
              xfree (reftbl);
              return err;
            }
          qsort (reftbl, refidx, sizeof *reftbl, sort_hostpool);
        }
      else
        xfree (reftbl);
    }

  hi = hosttable[idx];
  if (hi->pool)
    {
      /* Deal with the pool name before selecting a host. */
      if (r_poolname && hi->cname)
        {
          *r_poolname = xtrystrdup (hi->cname);
          if (!*r_poolname)
            return gpg_error_from_syserror ();
        }

      /* If the currently selected host is now marked dead, force a
         re-selection .  */
      if (force_reselect)
        hi->poolidx = -1;
      else if (hi->poolidx >= 0 && hi->poolidx < hosttable_size
               && hosttable[hi->poolidx] && hosttable[hi->poolidx]->dead)
        hi->poolidx = -1;

      /* Select a host if needed.  */
      if (hi->poolidx == -1)
        {
          hi->poolidx = select_random_host (hi->pool);
          if (hi->poolidx == -1)
            {
              log_error ("no alive host found in pool '%s'\n", name);
              if (r_poolname)
                {
                  xfree (*r_poolname);
                  *r_poolname = NULL;
                }
              return gpg_error (GPG_ERR_NO_KEYSERVER);
            }
        }

      assert (hi->poolidx >= 0 && hi->poolidx < hosttable_size);
      hi = hosttable[hi->poolidx];
      assert (hi);
    }

  if (hi->dead)
    {
      log_error ("host '%s' marked as dead\n", hi->name);
      if (r_poolname)
        {
          xfree (*r_poolname);
          *r_poolname = NULL;
        }
      return gpg_error (GPG_ERR_NO_KEYSERVER);
    }

  if (r_httpflags)
    {
      /* If the hosttable does not indicate that a certain host
         supports IPv<N>, we explicit set the corresponding http
         flags.  The reason for this is that a host might be listed in
         a pool as not v6 only but actually support v6 when later
         the name is resolved by our http layer.  */
      if (!hi->v4)
        *r_httpflags |= HTTP_FLAG_IGNORE_IPv4;
      if (!hi->v6)
        *r_httpflags |= HTTP_FLAG_IGNORE_IPv6;
    }

  *r_host = xtrystrdup (hi->name);
  if (!*r_host)
    {
      err = gpg_error_from_syserror ();
      if (r_poolname)
        {
          xfree (*r_poolname);
          *r_poolname = NULL;
        }
      return err;
    }
  return 0;
}
Exemplo n.º 2
0
/* Map the host name NAME to the actual to be used host name.  This
   allows us to manage round robin DNS names.  We use our own strategy
   to choose one of the hosts.  For example we skip those hosts which
   failed for some time and we stick to one host for a time
   independent of DNS retry times.  If FORCE_RESELECT is true a new
   host is always selected.  The selected host is stored as a malloced
   string at R_HOST; on error NULL is stored.  If we know the port
   used by the selected host, a string representation is written to
   R_PORTSTR, otherwise it is left untouched.  If R_HTTPFLAGS is not
   NULL it will receive flags which are to be passed to http_open.  If
   R_POOLNAME is not NULL a malloced name of the pool is stored or
   NULL if it is not a pool. */
static gpg_error_t
map_host (ctrl_t ctrl, const char *name, int force_reselect,
          char **r_host, char *r_portstr,
          unsigned int *r_httpflags, char **r_poolname)
{
  gpg_error_t err = 0;
  hostinfo_t hi;
  int idx;

  *r_host = NULL;
  if (r_httpflags)
    *r_httpflags = 0;
  if (r_poolname)
    *r_poolname = NULL;

  /* No hostname means localhost.  */
  if (!name || !*name)
    {
      *r_host = xtrystrdup ("localhost");
      return *r_host? 0 : gpg_error_from_syserror ();
    }

  /* See whether the host is in our table.  */
  idx = find_hostinfo (name);
  if (idx == -1 && is_onion_address (name))
    {
      idx = create_new_hostinfo (name);
      if (idx == -1)
        return gpg_error_from_syserror ();
      hi = hosttable[idx];
      hi->onion = 1;
    }
  else if (idx == -1)
    {
      /* We never saw this host.  Allocate a new entry.  */
      dns_addrinfo_t aibuf, ai;
      int *reftbl;
      size_t reftblsize;
      int refidx;
      int is_pool = 0;
      char *cname;
#ifdef	USE_DNS_SRV
      char *srvrecord;
      struct srventry *srvs;
      int srvscount;
#endif	/* USE_DNS_SRV */

      reftblsize = 100;
      reftbl = xtrymalloc (reftblsize * sizeof *reftbl);
      if (!reftbl)
        return gpg_error_from_syserror ();
      refidx = 0;

      idx = create_new_hostinfo (name);
      if (idx == -1)
        {
          err = gpg_error_from_syserror ();
          xfree (reftbl);
          return err;
        }
      hi = hosttable[idx];

#ifdef	USE_DNS_SRV
      /* Check for SRV records.  */
      srvrecord = xtryasprintf ("_hkp._tcp.%s", name);
      if (srvrecord == NULL)
        {
          err = gpg_error_from_syserror ();
          xfree (reftbl);
          return err;
        }

      srvscount = getsrv (srvrecord, &srvs);
      xfree (srvrecord);
      if (srvscount < 0)
        {
          err = gpg_error_from_syserror ();
          xfree (reftbl);
          return err;
        }

      if (srvscount > 0)
        {
          int i;
          is_pool = srvscount > 1;

          for (i = 0; i < srvscount; i++)
            {
              err = resolve_dns_name (srvs[i].target, 0,
                                      AF_UNSPEC, SOCK_STREAM,
                                      &ai, &cname);
              if (err)
                continue;
              dirmngr_tick (ctrl);
              add_host (name, is_pool, ai, srvs[i].port,
                        reftbl, reftblsize, &refidx);
            }

          xfree (srvs);
        }
#endif	/* USE_DNS_SRV */

      /* Find all A records for this entry and put them into the pool
         list - if any.  */
      err = resolve_dns_name (name, 0, 0, SOCK_STREAM, &aibuf, &cname);
      if (err)
        {
          log_error ("resolving '%s' failed: %s\n", name, gpg_strerror (err));
          err = 0;
        }
      else
        {
          /* First figure out whether this is a pool.  For a pool we
             use a different strategy than for a plain server: We use
             the canonical name of the pool as the virtual host along
             with the IP addresses.  If it is not a pool, we use the
             specified name. */
          if (! is_pool)
            is_pool = arecords_is_pool (aibuf);
          if (is_pool && cname)
            {
              hi->cname = cname;
              cname = NULL;
            }

          for (ai = aibuf; ai; ai = ai->next)
            {
              if (ai->family != AF_INET && ai->family != AF_INET6)
                continue;
              dirmngr_tick (ctrl);

              add_host (name, is_pool, ai, 0, reftbl, reftblsize, &refidx);
            }
        }
      reftbl[refidx] = -1;
      xfree (cname);
      free_dns_addrinfo (aibuf);

      if (refidx && is_pool)
        {
          assert (!hi->pool);
          hi->pool = xtryrealloc (reftbl, (refidx+1) * sizeof *reftbl);
          if (!hi->pool)
            {
              err = gpg_error_from_syserror ();
              log_error ("shrinking index table in map_host failed: %s\n",
                         gpg_strerror (err));
              xfree (reftbl);
              return err;
            }
          qsort (hi->pool, refidx, sizeof *reftbl, sort_hostpool);
        }
      else
        xfree (reftbl);
    }

  hi = hosttable[idx];
  if (hi->pool)
    {
      /* Deal with the pool name before selecting a host. */
      if (r_poolname)
        {
          *r_poolname = xtrystrdup (hi->cname? hi->cname : hi->name);
          if (!*r_poolname)
            return gpg_error_from_syserror ();
        }

      /* If the currently selected host is now marked dead, force a
         re-selection .  */
      if (force_reselect)
        hi->poolidx = -1;
      else if (hi->poolidx >= 0 && hi->poolidx < hosttable_size
               && hosttable[hi->poolidx] && hosttable[hi->poolidx]->dead)
        hi->poolidx = -1;

      /* Select a host if needed.  */
      if (hi->poolidx == -1)
        {
          hi->poolidx = select_random_host (hi->pool);
          if (hi->poolidx == -1)
            {
              log_error ("no alive host found in pool '%s'\n", name);
              if (r_poolname)
                {
                  xfree (*r_poolname);
                  *r_poolname = NULL;
                }
              return gpg_error (GPG_ERR_NO_KEYSERVER);
            }
        }

      assert (hi->poolidx >= 0 && hi->poolidx < hosttable_size);
      hi = hosttable[hi->poolidx];
      assert (hi);
    }

  if (hi->dead)
    {
      log_error ("host '%s' marked as dead\n", hi->name);
      if (r_poolname)
        {
          xfree (*r_poolname);
          *r_poolname = NULL;
        }
      return gpg_error (GPG_ERR_NO_KEYSERVER);
    }

  if (r_httpflags)
    {
      /* If the hosttable does not indicate that a certain host
         supports IPv<N>, we explicit set the corresponding http
         flags.  The reason for this is that a host might be listed in
         a pool as not v6 only but actually support v6 when later
         the name is resolved by our http layer.  */
      if (!hi->v4)
        *r_httpflags |= HTTP_FLAG_IGNORE_IPv4;
      if (!hi->v6)
        *r_httpflags |= HTTP_FLAG_IGNORE_IPv6;

      /* Note that we do not set the HTTP_FLAG_FORCE_TOR for onion
         addresses because the http module detects this itself.  This
         also allows us to use an onion address without Tor mode being
         enabled.  */
    }

  *r_host = xtrystrdup (hi->name);
  if (!*r_host)
    {
      err = gpg_error_from_syserror ();
      if (r_poolname)
        {
          xfree (*r_poolname);
          *r_poolname = NULL;
        }
      return err;
    }
  if (hi->port)
    snprintf (r_portstr, 6 /* five digits and the sentinel */,
              "%hu", hi->port);
  return 0;
}
Exemplo n.º 3
0
/* Map the host name NAME to the actual to be used host name.  This
 * allows us to manage round robin DNS names.  We use our own strategy
 * to choose one of the hosts.  For example we skip those hosts which
 * failed for some time and we stick to one host for a time
 * independent of DNS retry times.  If FORCE_RESELECT is true a new
 * host is always selected.  If SRVTAG is NULL no service record
 * lookup will be done, if it is set that service name is used.  The
 * selected host is stored as a malloced string at R_HOST; on error
 * NULL is stored.  If we know the port used by the selected host from
 * a service record, a string representation is written to R_PORTSTR,
 * otherwise it is left untouched.  If R_HTTPFLAGS is not NULL it will
 * receive flags which are to be passed to http_open.  If R_HTTPHOST
 * is not NULL a malloced name of the host is stored there; this might
 * be different from R_HOST in case it has been selected from a
 * pool.  */
static gpg_error_t
map_host (ctrl_t ctrl, const char *name, const char *srvtag, int force_reselect,
          enum ks_protocol protocol, char **r_host, char *r_portstr,
          unsigned int *r_httpflags, char **r_httphost)
{
  gpg_error_t err = 0;
  hostinfo_t hi;
  int idx;
  dns_addrinfo_t aibuf, ai;
  int is_pool;
  int new_hosts = 0;
  char *cname;

  *r_host = NULL;
  if (r_httpflags)
    *r_httpflags = 0;
  if (r_httphost)
    *r_httphost = NULL;

  /* No hostname means localhost.  */
  if (!name || !*name)
    {
      *r_host = xtrystrdup ("localhost");
      return *r_host? 0 : gpg_error_from_syserror ();
    }

  /* See whether the host is in our table.  */
  idx = find_hostinfo (name);
  if (idx == -1)
    {
      idx = create_new_hostinfo (name);
      if (idx == -1)
        return gpg_error_from_syserror ();
      hi = hosttable[idx];
      hi->onion = is_onion_address (name);
    }
  else
    hi = hosttable[idx];

  is_pool = hi->pool != NULL;

  if (srvtag && !is_ip_address (name)
      && ! hi->onion
      && ! (hi->did_srv_lookup & 1 << protocol))
    {
      struct srventry *srvs;
      unsigned int srvscount;

      /* Check for SRV records.  */
      err = get_dns_srv (name, srvtag, NULL, &srvs, &srvscount);
      if (err)
        {
          if (gpg_err_code (err) == GPG_ERR_ECONNREFUSED)
            tor_not_running_p (ctrl);
          return err;
        }

      if (srvscount > 0)
        {
          int i;
          if (! is_pool)
            is_pool = srvscount > 1;

          for (i = 0; i < srvscount; i++)
            {
              err = resolve_dns_name (srvs[i].target, 0,
                                      AF_UNSPEC, SOCK_STREAM,
                                      &ai, &cname);
              if (err)
                continue;
              dirmngr_tick (ctrl);
              add_host (name, is_pool, ai, protocol, srvs[i].port);
              new_hosts = 1;
            }

          xfree (srvs);
        }

      hi->did_srv_lookup |= 1 << protocol;
    }

  if (! hi->did_a_lookup
      && ! hi->onion)
    {
      /* Find all A records for this entry and put them into the pool
         list - if any.  */
      err = resolve_dns_name (name, 0, 0, SOCK_STREAM, &aibuf, &cname);
      if (err)
        {
          log_error ("resolving '%s' failed: %s\n", name, gpg_strerror (err));
          err = 0;
        }
      else
        {
          /* First figure out whether this is a pool.  For a pool we
             use a different strategy than for a plain server: We use
             the canonical name of the pool as the virtual host along
             with the IP addresses.  If it is not a pool, we use the
             specified name. */
          if (! is_pool)
            is_pool = arecords_is_pool (aibuf);
          if (is_pool && cname)
            {
              hi->cname = cname;
              cname = NULL;
            }

          for (ai = aibuf; ai; ai = ai->next)
            {
              if (ai->family != AF_INET && ai->family != AF_INET6)
                continue;
              if (opt.disable_ipv4 && ai->family == AF_INET)
                continue;
              if (opt.disable_ipv6 && ai->family == AF_INET6)
                continue;
              dirmngr_tick (ctrl);

              add_host (name, is_pool, ai, 0, 0);
              new_hosts = 1;
            }

          hi->did_a_lookup = 1;
        }
      xfree (cname);
      free_dns_addrinfo (aibuf);
    }
  if (new_hosts)
    hostinfo_sort_pool (hi);

  if (hi->pool)
    {
      /* Deal with the pool name before selecting a host. */
      if (r_httphost)
        {
          *r_httphost = xtrystrdup (hi->cname? hi->cname : hi->name);
          if (!*r_httphost)
            return gpg_error_from_syserror ();
        }

      /* If the currently selected host is now marked dead, force a
         re-selection .  */
      if (force_reselect)
        hi->poolidx = -1;
      else if (hi->poolidx >= 0 && hi->poolidx < hosttable_size
               && hosttable[hi->poolidx] && hosttable[hi->poolidx]->dead)
        hi->poolidx = -1;

      /* Select a host if needed.  */
      if (hi->poolidx == -1)
        {
          hi->poolidx = select_random_host (hi);
          if (hi->poolidx == -1)
            {
              log_error ("no alive host found in pool '%s'\n", name);
              if (r_httphost)
                {
                  xfree (*r_httphost);
                  *r_httphost = NULL;
                }
              return gpg_error (GPG_ERR_NO_KEYSERVER);
            }
        }

      assert (hi->poolidx >= 0 && hi->poolidx < hosttable_size);
      hi = hosttable[hi->poolidx];
      assert (hi);
    }
  else if (r_httphost && is_ip_address (hi->name))
    {
      /* This is a numerical IP address and not a pool.  We want to
       * find the canonical name so that it can be used in the HTTP
       * Host header.  Fixme: We should store that name in the
       * hosttable. */
      char *host;

      err = resolve_dns_name (hi->name, 0, 0, SOCK_STREAM, &aibuf, NULL);
      if (!err)
        {
          for (ai = aibuf; ai; ai = ai->next)
            {
              if ((!opt.disable_ipv6 && ai->family == AF_INET6)
                  || (!opt.disable_ipv4 && ai->family == AF_INET))
                {
                  err = resolve_dns_addr (ai->addr, ai->addrlen, 0, &host);
                  if (!err)
                    {
                      /* Okay, we return the first found name.  */
                      *r_httphost = host;
                      break;
                    }
                }
            }
        }
      free_dns_addrinfo (aibuf);
    }

  if (hi->dead)
    {
      log_error ("host '%s' marked as dead\n", hi->name);
      if (r_httphost)
        {
          xfree (*r_httphost);
          *r_httphost = NULL;
        }
      return gpg_error (GPG_ERR_NO_KEYSERVER);
    }

  if (r_httpflags)
    {
      /* If the hosttable does not indicate that a certain host
         supports IPv<N>, we explicit set the corresponding http
         flags.  The reason for this is that a host might be listed in
         a pool as not v6 only but actually support v6 when later
         the name is resolved by our http layer.  */
      if (!hi->v4)
        *r_httpflags |= HTTP_FLAG_IGNORE_IPv4;
      if (!hi->v6)
        *r_httpflags |= HTTP_FLAG_IGNORE_IPv6;

      /* Note that we do not set the HTTP_FLAG_FORCE_TOR for onion
         addresses because the http module detects this itself.  This
         also allows us to use an onion address without Tor mode being
         enabled.  */
    }

  *r_host = xtrystrdup (hi->name);
  if (!*r_host)
    {
      err = gpg_error_from_syserror ();
      if (r_httphost)
        {
          xfree (*r_httphost);
          *r_httphost = NULL;
        }
      return err;
    }
  if (hi->port[protocol])
    snprintf (r_portstr, 6 /* five digits and the sentinel */,
              "%hu", hi->port[protocol]);
  return 0;
}
Exemplo n.º 4
0
/* Map the host name NAME to the actual to be used host name.  This
   allows us to manage round robin DNS names.  We use our own strategy
   to choose one of the hosts.  For example we skip those hosts which
   failed for some time and we stick to one host for a time
   independent of DNS retry times.  */
static char *
map_host (const char *name)
{
  hostinfo_t hi;
  int idx;

  /* No hostname means localhost.  */
  if (!name || !*name)
    return xtrystrdup ("localhost");

  /* See whether the host is in our table.  */
  idx = find_hostinfo (name);
  if (idx == -1)
    {
      /* We never saw this host.  Allocate a new entry.  */
      struct addrinfo hints, *aibuf, *ai;
      int *reftbl;
      size_t reftblsize;
      int refidx;

      reftblsize = 100;
      reftbl = xtrymalloc (reftblsize * sizeof *reftbl);
      if (!reftbl)
        return NULL;
      refidx = 0;

      idx = create_new_hostinfo (name);
      if (idx == -1)
        {
          xfree (reftbl);
          return NULL;
        }
      hi = hosttable[idx];

      /* Find all A records for this entry and put them into the pool
         list - if any.  */
      memset (&hints, 0, sizeof (hints));
      hints.ai_socktype = SOCK_STREAM;
      if (!getaddrinfo (name, NULL, &hints, &aibuf))
        {
          for (ai = aibuf; ai; ai = ai->ai_next)
            {
              char tmphost[NI_MAXHOST];
              int tmpidx;
              int ec;
              int i;

              if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
                continue;

              log_printhex ("getaddrinfo returned", ai->ai_addr,ai->ai_addrlen);
              if ((ec=getnameinfo (ai->ai_addr, ai->ai_addrlen,
                                   tmphost, sizeof tmphost,
                                   NULL, 0, NI_NAMEREQD)))
                log_info ("getnameinfo failed while checking '%s': %s\n",
                          name, gai_strerror (ec));
              else if (refidx+1 >= reftblsize)
                {
                  log_error ("getnameinfo returned for '%s': '%s'"
                            " [index table full - ignored]\n", name, tmphost);
                }
              else
                {

                  if ((tmpidx = find_hostinfo (tmphost)) != -1)
                    {
                      log_info ("getnameinfo returned for '%s': '%s'"
                                " [already known]\n", name, tmphost);
                      if (ai->ai_family == AF_INET)
                        hosttable[tmpidx]->v4 = 1;
                      if (ai->ai_family == AF_INET6)
                        hosttable[tmpidx]->v6 = 1;

                      for (i=0; i < refidx; i++)
                        if (reftbl[i] == tmpidx)
                      break;
                      if (!(i < refidx) && tmpidx != idx)
                        reftbl[refidx++] = tmpidx;
                    }
                  else
                    {
                      log_info ("getnameinfo returned for '%s': '%s'\n",
                                name, tmphost);
                      /* Create a new entry.  */
                      tmpidx = create_new_hostinfo (tmphost);
                      if (tmpidx == -1)
                        log_error ("map_host for '%s' problem: %s - '%s'"
                                   " [ignored]\n",
                                   name, strerror (errno), tmphost);
                      else
                        {
                          if (ai->ai_family == AF_INET)
                            hosttable[tmpidx]->v4 = 1;
                          if (ai->ai_family == AF_INET6)
                            hosttable[tmpidx]->v6 = 1;

                          for (i=0; i < refidx; i++)
                            if (reftbl[i] == tmpidx)
                              break;
                          if (!(i < refidx) && tmpidx != idx)
                            reftbl[refidx++] = tmpidx;
                        }
                    }
                }
            }
        }
      reftbl[refidx] = -1;
      if (refidx)
        {
          assert (!hi->pool);
          hi->pool = xtryrealloc (reftbl, (refidx+1) * sizeof *reftbl);
          if (!hi->pool)
            {
              log_error ("shrinking index table in map_host failed: %s\n",
                         strerror (errno));
              xfree (reftbl);
            }
          qsort (reftbl, refidx, sizeof *reftbl, sort_hostpool);
        }
      else
        xfree (reftbl);
    }

  hi = hosttable[idx];
  if (hi->pool)
    {
      /* If the currently selected host is now marked dead, force a
         re-selection .  */
      if (hi->poolidx >= 0 && hi->poolidx < hosttable_size
          && hosttable[hi->poolidx] && hosttable[hi->poolidx]->dead)
        hi->poolidx = -1;

      /* Select a host if needed.  */
      if (hi->poolidx == -1)
        {
          hi->poolidx = select_random_host (hi->pool);
          if (hi->poolidx == -1)
            {
              log_error ("no alive host found in pool '%s'\n", name);
              return NULL;
            }
        }

      assert (hi->poolidx >= 0 && hi->poolidx < hosttable_size);
      hi = hosttable[hi->poolidx];
      assert (hi);
    }

  if (hi->dead)
    {
      log_error ("host '%s' marked as dead\n", hi->name);
      return NULL;
    }

  return xtrystrdup (hi->name);
}