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->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;
}