build: Require at least Libassuan 2.4.1.
[gnupg.git] / dirmngr / http.c
index b10ba25..74b6911 100644 (file)
@@ -1,23 +1,33 @@
 /* http.c  -  HTTP protocol handler
- * Copyright (C) 1999, 2001, 2002, 2003, 2004,
- *               2006, 2009  Free Software Foundation, Inc.
+ * Copyright (C) 1999, 2001, 2002, 2003, 2004, 2006, 2009, 2010,
+ *               2011 Free Software Foundation, Inc.
+ * Copyright (C) 2014 Werner Koch
+ * Copyright (C) 2015 g10 Code GmbH
  *
  * This file is part of GnuPG.
  *
- * GnuPG is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of either
  *
- * GnuPG is distributed in the hope that it will be useful,
+ *   - the GNU Lesser General Public License as published by the Free
+ *     Software Foundation; either version 3 of the License, or (at
+ *     your option) any later version.
+ *
+ * or
+ *
+ *   - the GNU General Public License as published by the Free
+ *     Software Foundation; either version 2 of the License, or (at
+ *     your option) any later version.
+ *
+ * or both in parallel, as here.
+ *
+ * This file is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
- * USA.
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
 /* Simple HTTP client implementation.  We try to keep the code as
@@ -30,8 +40,9 @@
   - fixme: list other requirements.
 
 
-  - With HTTP_USE_GNUTLS support for https is provided (this also
-    requires estream).
+  - With HTTP_USE_NTBTLS or HTTP_USE_GNUTLS support for https is
+    provided (this also requires estream).
+
   - With HTTP_NO_WSASTARTUP the socket initialization is not done
     under Windows.  This is useful if the socket layer has already
     been initialized elsewhere.  This also avoids the installation of
@@ -50,6 +61,9 @@
 #include <unistd.h>
 
 #ifdef HAVE_W32_SYSTEM
+# ifdef HAVE_WINSOCK2_H
+#  include <winsock2.h>
+# endif
 # include <windows.h>
 #else /*!HAVE_W32_SYSTEM*/
 # include <sys/types.h>
 # include <netdb.h>
 #endif /*!HAVE_W32_SYSTEM*/
 
-#include <pth.h>
+#ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth.  */
+# undef USE_NPTH
+#endif
 
-#ifdef HTTP_USE_GNUTLS
+#ifdef USE_NPTH
+# include <npth.h>
+#endif
+
+#if defined (HTTP_USE_GNUTLS) && defined (HTTP_USE_NTBTLS)
+# error Both, HTTP_USE_GNUTLS and HTTP_USE_NTBTLS, are defined.
+#endif
+
+#ifdef HTTP_USE_NTBTLS
+# include <ntbtls.h>
+#elif HTTP_USE_GNUTLS
 # include <gnutls/gnutls.h>
-/* For non-understandable reasons GNUTLS dropped the _t suffix from
-   all types. yes, ISO-C might be read as this but there are still
-   other name space conflicts and using _t is actually a Good
-   Thing. */
-typedef gnutls_session gnutls_session_t;
-typedef gnutls_transport_ptr gnutls_transport_ptr_t;
+# include <gnutls/x509.h>
 #endif /*HTTP_USE_GNUTLS*/
 
-#ifdef TEST
-#undef USE_DNS_SRV
-#endif
+#include <assuan.h>  /* We need the socket wrapper.  */
 
 #include "util.h"
 #include "i18n.h"
+#include "dns-stuff.h"
 #include "http.h"
-#ifdef USE_DNS_SRV
-#include "srv.h"
-#else /*!USE_DNS_SRV*/
-/* If we are not compiling with SRV record support we provide stub
-   data structures. */
-#ifndef MAXDNAME
-#define MAXDNAME 1025
-#endif
-struct srventry
-{
-  unsigned short priority;
-  unsigned short weight;
-  unsigned short port;
-  int run_count;
-  char target[MAXDNAME];
-};
-#endif/*!USE_DNS_SRV*/
 
 
+#ifdef USE_NPTH
+# define my_select(a,b,c,d,e)  npth_select ((a), (b), (c), (d), (e))
+# define my_accept(a,b,c)      npth_accept ((a), (b), (c))
+#else
+# define my_select(a,b,c,d,e)  select ((a), (b), (c), (d), (e))
+# define my_accept(a,b,c)      accept ((a), (b), (c))
+#endif
+
 #ifdef HAVE_W32_SYSTEM
 #define sock_close(a)  closesocket(a)
 #else
@@ -108,6 +119,9 @@ struct srventry
 #ifndef EAGAIN
 #define EAGAIN  EWOULDBLOCK
 #endif
+#ifndef INADDR_NONE  /* Slowaris is missing that.  */
+#define INADDR_NONE  ((unsigned long)(-1))
+#endif /*INADDR_NONE*/
 
 #define HTTP_PROXY_ENV           "http_proxy"
 #define MAX_LINELEN 20000  /* Max. length of a HTTP header line. */
@@ -119,33 +133,58 @@ struct srventry
 /* A long counter type.  */
 #ifdef HAVE_STRTOULL
 typedef unsigned long long longcounter_t;
-#define counter_strtoul(a) strtoull ((a), NULL, 10)
+# define counter_strtoul(a) strtoull ((a), NULL, 10)
 #else
 typedef unsigned long longcounter_t;
-#define counter_strtoul(a) strtoul ((a), NULL, 10)
+# define counter_strtoul(a) strtoul ((a), NULL, 10)
 #endif
 
-#ifndef HTTP_USE_GNUTLS
-typedef void * gnutls_session_t;
+#if HTTP_USE_NTBTLS
+typedef ntbtls_t         tls_session_t;
+# define USE_TLS 1
+#elif HTTP_USE_GNUTLS
+typedef gnutls_session_t tls_session_t;
+# define USE_TLS 1
+#else
+typedef void *tls_session_t;
+# undef USE_TLS
 #endif
 
-static gpg_error_t do_parse_uri (parsed_uri_t uri, int only_local_part);
+static gpg_err_code_t do_parse_uri (parsed_uri_t uri, int only_local_part,
+                                    int no_scheme_check, int force_tls);
+static gpg_error_t parse_uri (parsed_uri_t *ret_uri, const char *uri,
+                              int no_scheme_check, int force_tls);
 static int remove_escapes (char *string);
 static int insert_escapes (char *buffer, const char *string,
                            const char *special);
 static uri_tuple_t parse_tuple (char *string);
-static gpg_error_t send_request (http_t hd,
-                                 const char *auth, const char *proxy);
+static gpg_error_t send_request (http_t hd, const char *httphost,
+                                 const char *auth,const char *proxy,
+                                const char *srvtag,strlist_t headers);
 static char *build_rel_path (parsed_uri_t uri);
 static gpg_error_t parse_response (http_t hd);
 
-static int connect_server (const char *server, unsigned short port,
-                           unsigned int flags, const char *srvtag);
+static assuan_fd_t connect_server (const char *server, unsigned short port,
+                                   unsigned int flags, const char *srvtag,
+                                   int *r_host_not_found);
+static gpg_error_t write_server (int sock, const char *data, size_t length);
 
-static ssize_t cookie_read (void *cookie, void *buffer, size_t size);
-static ssize_t cookie_write (void *cookie, const void *buffer, size_t size);
+static gpgrt_ssize_t cookie_read (void *cookie, void *buffer, size_t size);
+static gpgrt_ssize_t cookie_write (void *cookie,
+                                   const void *buffer, size_t size);
 static int cookie_close (void *cookie);
 
+
+/* A socket object used to a allow ref counting of sockets.  */
+struct my_socket_s
+{
+  assuan_fd_t fd; /* The actual socket - shall never be ASSUAN_INVALID_FD.  */
+  int refcount;   /* Number of references to this socket.  */
+};
+typedef struct my_socket_s *my_socket_t;
+
+
+/* Cookie function structure and cookie object.  */
 static es_cookie_io_functions_t cookie_functions =
   {
     cookie_read,
@@ -154,25 +193,44 @@ static es_cookie_io_functions_t cookie_functions =
     cookie_close
   };
 
-struct cookie_s 
+struct cookie_s
 {
-  int fd;  /* File descriptor or -1 if already closed. */
-  gnutls_session_t tls_session;  /* TLS session context or NULL if not used. */
+  /* Socket object or NULL if already closed. */
+  my_socket_t sock;
+
+  /* The session object or NULL if not used. */
+  http_session_t session;
+
+  /* True if TLS is to be used.  */
+  int use_tls;
 
   /* The remaining content length and a flag telling whether to use
      the content length.  */
-  longcounter_t content_length;  
+  longcounter_t content_length;
   unsigned int content_length_valid:1;
-
-  /* Flag to communicate with the close handler. */
-  unsigned int keep_socket:1;
 };
 typedef struct cookie_s *cookie_t;
 
-
+/* The session object. */
+struct http_session_s
+{
+  int refcount;    /* Number of references to this object.  */
 #ifdef HTTP_USE_GNUTLS
-static gpg_error_t (*tls_callback) (http_t, gnutls_session_t, int);
+  gnutls_certificate_credentials_t certcred;
 #endif /*HTTP_USE_GNUTLS*/
+#ifdef USE_TLS
+  tls_session_t tls_session;
+  struct {
+    int done;      /* Verifciation has been done.  */
+    int rc;        /* TLS verification return code.  */
+    unsigned int status; /* Verification status.  */
+  } verify;
+  char *servername; /* Malloced server name.  */
+#endif /*USE_TLS*/
+  /* A callback function to log details of TLS certifciates.  */
+  void (*cert_log_cb) (http_session_t, gpg_error_t, const char *,
+                       const void **, size_t *);
+};
 
 
 /* An object to save header lines. */
@@ -186,17 +244,17 @@ typedef struct header_s *header_t;
 
 
 /* Our handle context. */
-struct http_context_s 
+struct http_context_s
 {
   unsigned int status_code;
-  int sock;
+  my_socket_t sock;
   unsigned int in_data:1;
   unsigned int is_http_0_9:1;
   estream_t fp_read;
   estream_t fp_write;
   void *write_cookie;
   void *read_cookie;
-  void *tls_context;
+  http_session_t session;
   parsed_uri_t uri;
   http_req_t req_type;
   char *buffer;          /* Line buffer. */
@@ -206,6 +264,12 @@ struct http_context_s
 };
 
 
+/* The global callback for the verification function.  */
+static gpg_error_t (*tls_callback) (http_t, http_session_t, int);
+
+/* The list of files with trusted CA certificates.  */
+static strlist_t tls_ca_certlist;
+
 
 \f
 #if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
@@ -234,14 +298,14 @@ init_sockets (void)
   if (initialized)
     return;
 
-  if ( WSAStartup( MAKEWORD (REQ_WINSOCK_MINOR, REQ_WINSOCK_MAJOR), &wsdata ) ) 
+  if ( WSAStartup( MAKEWORD (REQ_WINSOCK_MINOR, REQ_WINSOCK_MAJOR), &wsdata ) )
     {
-      log_error ("error initializing socket library: ec=%d\n", 
+      log_error ("error initializing socket library: ec=%d\n",
                  (int)WSAGetLastError () );
       return;
     }
-  if ( LOBYTE(wsdata.wVersion) != REQ_WINSOCK_MAJOR  
-       || HIBYTE(wsdata.wVersion) != REQ_WINSOCK_MINOR ) 
+  if ( LOBYTE(wsdata.wVersion) != REQ_WINSOCK_MAJOR
+       || HIBYTE(wsdata.wVersion) != REQ_WINSOCK_MINOR )
     {
       log_error ("socket library version is %x.%x - but %d.%d needed\n",
                  LOBYTE(wsdata.wVersion), HIBYTE(wsdata.wVersion),
@@ -255,6 +319,110 @@ init_sockets (void)
 #endif /*HAVE_W32_SYSTEM && !HTTP_NO_WSASTARTUP*/
 
 
+/* Create a new socket object.  Returns NULL and closes FD if not
+   enough memory is available.  */
+static my_socket_t
+_my_socket_new (int lnr, assuan_fd_t fd)
+{
+  my_socket_t so;
+
+  so = xtrymalloc (sizeof *so);
+  if (!so)
+    {
+      int save_errno = errno;
+      assuan_sock_close (fd);
+      gpg_err_set_errno (save_errno);
+      return NULL;
+    }
+  so->fd = fd;
+  so->refcount = 1;
+  /* log_debug ("http.c:socket_new(%d): object %p for fd %d created\n", */
+  /*            lnr, so, so->fd); */
+  (void)lnr;
+  return so;
+}
+#define my_socket_new(a) _my_socket_new (__LINE__, (a))
+
+/* Bump up the reference counter for the socket object SO.  */
+static my_socket_t
+_my_socket_ref (int lnr, my_socket_t so)
+{
+  so->refcount++;
+  /* log_debug ("http.c:socket_ref(%d) object %p for fd %d refcount now %d\n", */
+  /*            lnr, so, so->fd, so->refcount); */
+  (void)lnr;
+  return so;
+}
+#define my_socket_ref(a) _my_socket_ref (__LINE__,(a))
+
+
+/* Bump down the reference counter for the socket object SO.  If SO
+   has no more references, close the socket and release the
+   object.  */
+static void
+_my_socket_unref (int lnr, my_socket_t so,
+                  void (*preclose)(void*), void *preclosearg)
+{
+  if (so)
+    {
+      so->refcount--;
+      /* log_debug ("http.c:socket_unref(%d): object %p for fd %d ref now %d\n", */
+      /*            lnr, so, so->fd, so->refcount); */
+      (void)lnr;
+      if (!so->refcount)
+        {
+          if (preclose)
+            preclose (preclosearg);
+          assuan_sock_close (so->fd);
+          xfree (so);
+        }
+    }
+}
+#define my_socket_unref(a,b,c) _my_socket_unref (__LINE__,(a),(b),(c))
+
+
+#ifdef HTTP_USE_GNUTLS
+static ssize_t
+my_gnutls_read (gnutls_transport_ptr_t ptr, void *buffer, size_t size)
+{
+  my_socket_t sock = ptr;
+#if USE_NPTH
+  return npth_read (sock->fd, buffer, size);
+#else
+  return read (sock->fd, buffer, size);
+#endif
+}
+static ssize_t
+my_gnutls_write (gnutls_transport_ptr_t ptr, const void *buffer, size_t size)
+{
+  my_socket_t sock = ptr;
+#if USE_NPTH
+  return npth_write (sock->fd, buffer, size);
+#else
+  return write (sock->fd, buffer, size);
+#endif
+}
+#endif /*HTTP_USE_GNUTLS*/
+
+
+
+\f
+/* This notification function is called by estream whenever stream is
+   closed.  Its purpose is to mark the closing in the handle so
+   that a http_close won't accidentally close the estream.  The function
+   http_close removes this notification so that it won't be called if
+   http_close was used before an es_fclose.  */
+static void
+fp_onclose_notification (estream_t stream, void *opaque)
+{
+  http_t hd = opaque;
+
+  if (hd->fp_read && hd->fp_read == stream)
+    hd->fp_read = NULL;
+  else if (hd->fp_write && hd->fp_write == stream)
+    hd->fp_write = NULL;
+}
+
 
 /*
  * Helper function to create an HTTP header with hex encoded data.  A
@@ -264,13 +432,13 @@ init_sockets (void)
  */
 static char *
 make_header_line (const char *prefix, const char *suffix,
-                   const void *data, size_t len )
+                  const void *data, size_t len )
 {
-  static unsigned char bintoasc[] = 
+  static unsigned char bintoasc[] =
     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
     "abcdefghijklmnopqrstuvwxyz"
     "0123456789+/";
-  const unsigned int *s = data;
+  const unsigned char *s = data;
   char *buffer, *p;
 
   buffer = xtrymalloc (strlen (prefix) + (len+2)/3*4 + strlen (suffix) + 1);
@@ -283,8 +451,9 @@ make_header_line (const char *prefix, const char *suffix,
       *p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077];
       *p++ = bintoasc[(((s[1]<<2)&074)|((s[2]>>6)&03))&077];
       *p++ = bintoasc[s[2]&077];
+      *p = 0;
     }
-  if ( len == 2 ) 
+  if ( len == 2 )
     {
       *p++ = bintoasc[(s[0] >> 2) & 077];
       *p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077];
@@ -298,6 +467,7 @@ make_header_line (const char *prefix, const char *suffix,
       *p++ = '=';
       *p++ = '=';
     }
+  *p = 0;
   strcpy (p, suffix);
   return buffer;
 }
@@ -305,61 +475,369 @@ make_header_line (const char *prefix, const char *suffix,
 
 
 \f
+/* Register a non-standard global TLS callback function.  If no
+   verification is desired a callback needs to be registered which
+   always returns NULL.  */
 void
-http_register_tls_callback ( gpg_error_t (*cb) (http_t, void *, int) )
+http_register_tls_callback (gpg_error_t (*cb)(http_t, http_session_t, int))
 {
-#ifdef HTTP_USE_GNUTLS
-  tls_callback = (gpg_error_t (*) (http_t, gnutls_session_t, int))cb;
-#else
-  (void)cb;
-#endif  
+  tls_callback = cb;
+}
+
+
+/* Register a CA certificate for future use.  The certificate is
+   expected to be in FNAME.  PEM format is assume if FNAME has a
+   suffix of ".pem".  If FNAME is NULL the list of CA files is
+   removed.  */
+void
+http_register_tls_ca (const char *fname)
+{
+  strlist_t sl;
+
+  if (!fname)
+    {
+      free_strlist (tls_ca_certlist);
+      tls_ca_certlist = NULL;
+    }
+  else
+    {
+      sl = add_to_strlist (&tls_ca_certlist, fname);
+      if (*sl->d && !strcmp (sl->d + strlen (sl->d) - 4, ".pem"))
+        sl->flags = 1;
+    }
+}
+
+
+#ifdef USE_TLS
+/* Free the TLS session associated with SESS, if any.  */
+static void
+close_tls_session (http_session_t sess)
+{
+  if (sess->tls_session)
+    {
+# ifdef HTTP_USE_GNUTLS
+      my_socket_t sock = gnutls_transport_get_ptr (sess->tls_session);
+      my_socket_unref (sock, NULL, NULL);
+      gnutls_deinit (sess->tls_session);
+      if (sess->certcred)
+        gnutls_certificate_free_credentials (sess->certcred);
+# endif /*HTTP_USE_GNUTLS*/
+      xfree (sess->servername);
+      sess->tls_session = NULL;
+    }
+}
+#endif /*USE_TLS*/
+
+
+/* Release a session.  Take care not to release it while it is being
+   used by a http context object.  */
+static void
+session_unref (int lnr, http_session_t sess)
+{
+  if (!sess)
+    return;
+
+  sess->refcount--;
+  /* log_debug ("http.c:session_unref(%d): sess %p ref now %d\n", */
+  /*            lnr, sess, sess->refcount); */
+  (void)lnr;
+  if (sess->refcount)
+    return;
+
+#ifdef USE_TLS
+  close_tls_session (sess);
+#endif /*USE_TLS*/
+
+  xfree (sess);
+}
+#define http_session_unref(a) session_unref (__LINE__, (a))
+
+void
+http_session_release (http_session_t sess)
+{
+  http_session_unref (sess);
+}
+
+
+/* Create a new session object which is currently used to enable TLS
+   support.  It may eventually allow reusing existing connections.  */
+gpg_error_t
+http_session_new (http_session_t *r_session, const char *tls_priority)
+{
+  gpg_error_t err;
+  http_session_t sess;
+
+  *r_session = NULL;
+
+  sess = xtrycalloc (1, sizeof *sess);
+  if (!sess)
+    return gpg_error_from_syserror ();
+  sess->refcount = 1;
+
+#if HTTP_USE_NTBTLS
+  {
+    (void)tls_priority;
+
+    err = ntbtls_new (&sess->tls_session, NTBTLS_CLIENT);
+    if (err)
+      {
+        log_error ("ntbtls_new failed: %s\n", gpg_strerror (err));
+        goto leave;
+      }
+  }
+#elif HTTP_USE_GNUTLS
+  {
+    const char *errpos;
+    int rc;
+    strlist_t sl;
+
+    rc = gnutls_certificate_allocate_credentials (&sess->certcred);
+    if (rc < 0)
+      {
+        log_error ("gnutls_certificate_allocate_credentials failed: %s\n",
+                   gnutls_strerror (rc));
+        err = gpg_error (GPG_ERR_GENERAL);
+        goto leave;
+      }
+
+    for (sl = tls_ca_certlist; sl; sl = sl->next)
+      {
+        rc = gnutls_certificate_set_x509_trust_file
+          (sess->certcred, sl->d,
+           (sl->flags & 1)? GNUTLS_X509_FMT_PEM : GNUTLS_X509_FMT_DER);
+        if (rc < 0)
+          log_info ("setting CA from file '%s' failed: %s\n",
+                    sl->d, gnutls_strerror (rc));
+      }
+
+    rc = gnutls_init (&sess->tls_session, GNUTLS_CLIENT);
+    if (rc < 0)
+      {
+        log_error ("gnutls_init failed: %s\n", gnutls_strerror (rc));
+        err = gpg_error (GPG_ERR_GENERAL);
+        goto leave;
+      }
+    /* A new session has the transport ptr set to (void*(-1), we need
+       it to be NULL.  */
+    gnutls_transport_set_ptr (sess->tls_session, NULL);
+
+    rc = gnutls_priority_set_direct (sess->tls_session,
+                                     tls_priority? tls_priority : "NORMAL",
+                                     &errpos);
+    if (rc < 0)
+      {
+        log_error ("gnutls_priority_set_direct failed at '%s': %s\n",
+                   errpos, gnutls_strerror (rc));
+        err = gpg_error (GPG_ERR_GENERAL);
+        goto leave;
+      }
+
+    rc = gnutls_credentials_set (sess->tls_session,
+                                 GNUTLS_CRD_CERTIFICATE, sess->certcred);
+    if (rc < 0)
+      {
+        log_error ("gnutls_credentials_set failed: %s\n", gnutls_strerror (rc));
+        err = gpg_error (GPG_ERR_GENERAL);
+        goto leave;
+      }
+  }
+#else /*!HTTP_USE_GNUTLS*/
+  {
+    (void)tls_priority;
+  }
+#endif /*!HTTP_USE_GNUTLS*/
+
+  /* log_debug ("http.c:session_new: sess %p created\n", sess); */
+  err = 0;
+
+#if USE_TLS
+ leave:
+#endif /*USE_TLS*/
+  if (err)
+    http_session_unref (sess);
+  else
+    *r_session = sess;
+
+  return err;
+}
+
+
+/* Increment the reference count for session SESS.  Passing NULL for
+   SESS is allowed. */
+http_session_t
+http_session_ref (http_session_t sess)
+{
+  if (sess)
+    {
+      sess->refcount++;
+      /* log_debug ("http.c:session_ref: sess %p ref now %d\n", sess, */
+      /*            sess->refcount); */
+    }
+  return sess;
+}
+
+
+void
+http_session_set_log_cb (http_session_t sess,
+                         void (*cb)(http_session_t, gpg_error_t,
+                                    const char *hostname,
+                                    const void **certs, size_t *certlens))
+{
+  sess->cert_log_cb = cb;
 }
 
 
 
-/* Start a HTTP retrieval and return on success in R_HD a context
-   pointer for completing the the request and to wait for the
-   response. */
+\f
+/* Start a HTTP retrieval and on success store at R_HD a context
+   pointer for completing the request and to wait for the response.
+   If HTTPHOST is not NULL it is used hor the Host header instead of a
+   Host header derived from the URL. */
 gpg_error_t
-http_open (http_t *r_hd, http_req_t reqtype, const char *url, 
+http_open (http_t *r_hd, http_req_t reqtype, const char *url,
+           const char *httphost,
            const char *auth, unsigned int flags, const char *proxy,
-           void *tls_context)
+           http_session_t session, const char *srvtag, strlist_t headers)
 {
   gpg_error_t err;
   http_t hd;
-  
+
   *r_hd = NULL;
 
   if (!(reqtype == HTTP_REQ_GET || reqtype == HTTP_REQ_POST))
-    return gpg_error (GPG_ERR_INV_ARG);
-
-  /* Make need_header default unless ignore_cl is set.  We might want
-     to drop the need_header entirely.  */
-  if (!(flags & HTTP_FLAG_IGNORE_CL))
-    flags |= HTTP_FLAG_NEED_HEADER;
+    return gpg_err_make (default_errsource, GPG_ERR_INV_ARG);
 
   /* Create the handle. */
   hd = xtrycalloc (1, sizeof *hd);
   if (!hd)
     return gpg_error_from_syserror ();
-  hd->sock = -1;
   hd->req_type = reqtype;
   hd->flags = flags;
-  hd->tls_context = tls_context;
+  hd->session = http_session_ref (session);
+
+  err = parse_uri (&hd->uri, url, 0, !!(flags & HTTP_FLAG_FORCE_TLS));
+  if (!err)
+    err = send_request (hd, httphost, auth, proxy, srvtag, headers);
+
+  if (err)
+    {
+      my_socket_unref (hd->sock, NULL, NULL);
+      if (hd->fp_read)
+        es_fclose (hd->fp_read);
+      if (hd->fp_write)
+        es_fclose (hd->fp_write);
+      http_session_unref (hd->session);
+      xfree (hd);
+    }
+  else
+    *r_hd = hd;
+  return err;
+}
+
+
+/* This function is useful to connect to a generic TCP service using
+   this http abstraction layer.  This has the advantage of providing
+   service tags and an estream interface.  */
+gpg_error_t
+http_raw_connect (http_t *r_hd, const char *server, unsigned short port,
+                  unsigned int flags, const char *srvtag)
+{
+  gpg_error_t err = 0;
+  http_t hd;
+  cookie_t cookie;
+  int hnf;
+
+  *r_hd = NULL;
+
+  if ((flags & HTTP_FLAG_FORCE_TOR))
+    {
+      int mode;
+
+      if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
+        {
+          log_error ("Tor support is not available\n");
+          return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
+        }
+    }
+
+  /* Create the handle. */
+  hd = xtrycalloc (1, sizeof *hd);
+  if (!hd)
+    return gpg_error_from_syserror ();
+  hd->req_type = HTTP_REQ_OPAQUE;
+  hd->flags = flags;
+
+  /* Connect.  */
+  {
+    assuan_fd_t sock;
+
+    sock = connect_server (server, port, hd->flags, srvtag, &hnf);
+    if (sock == ASSUAN_INVALID_FD)
+      {
+        err = gpg_err_make (default_errsource,
+                            (hnf? GPG_ERR_UNKNOWN_HOST
+                             : gpg_err_code_from_syserror ()));
+        xfree (hd);
+        return err;
+      }
+    hd->sock = my_socket_new (sock);
+    if (!hd->sock)
+      {
+        err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+        xfree (hd);
+        return err;
+      }
+  }
+
+  /* Setup estreams for reading and writing.  */
+  cookie = xtrycalloc (1, sizeof *cookie);
+  if (!cookie)
+    {
+      err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+      goto leave;
+    }
+  cookie->sock = my_socket_ref (hd->sock);
+  hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
+  if (!hd->fp_write)
+    {
+      err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+      my_socket_unref (cookie->sock, NULL, NULL);
+      xfree (cookie);
+      goto leave;
+    }
+  hd->write_cookie = cookie; /* Cookie now owned by FP_WRITE.  */
+
+  cookie = xtrycalloc (1, sizeof *cookie);
+  if (!cookie)
+    {
+      err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+      goto leave;
+    }
+  cookie->sock = my_socket_ref (hd->sock);
+  hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
+  if (!hd->fp_read)
+    {
+      err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+      my_socket_unref (cookie->sock, NULL, NULL);
+      xfree (cookie);
+      goto leave;
+    }
+  hd->read_cookie = cookie; /* Cookie now owned by FP_READ.  */
 
-  err = http_parse_uri (&hd->uri, url);
+  /* Register close notification to interlock the use of es_fclose in
+     http_close and in user code.  */
+  err = es_onclose (hd->fp_write, 1, fp_onclose_notification, hd);
   if (!err)
-    err = send_request (hd, auth, proxy);
-  
+    err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd);
+
+ leave:
   if (err)
     {
-      if (!hd->fp_read && !hd->fp_write && hd->sock != -1)
-        sock_close (hd->sock);
       if (hd->fp_read)
         es_fclose (hd->fp_read);
       if (hd->fp_write)
         es_fclose (hd->fp_write);
-      http_release_parsed_uri (hd->uri);
+      my_socket_unref (hd->sock, NULL, NULL);
       xfree (hd);
     }
   else
@@ -368,6 +846,8 @@ http_open (http_t *r_hd, http_req_t reqtype, const char *url,
 }
 
 
+
+
 void
 http_start_data (http_t hd)
 {
@@ -389,43 +869,51 @@ http_wait_response (http_t hd)
   cookie_t cookie;
 
   /* Make sure that we are in the data. */
-  http_start_data (hd);        
+  http_start_data (hd);
 
+  /* Close the write stream.  Note that the reference counted socket
+     object keeps the actual system socket open.  */
   cookie = hd->write_cookie;
   if (!cookie)
-    return gpg_error (GPG_ERR_INTERNAL);
+    return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
 
-  /* Close the write stream but keep the socket open.  */
-  cookie->keep_socket = 1;
   es_fclose (hd->fp_write);
   hd->fp_write = NULL;
+  /* The close has released the cookie and thus we better set it to NULL.  */
   hd->write_cookie = NULL;
 
   /* Shutdown one end of the socket is desired.  As per HTTP/1.0 this
      is not required but some very old servers (e.g. the original pksd
      key server didn't worked without it.  */
   if ((hd->flags & HTTP_FLAG_SHUTDOWN))
-    shutdown (hd->sock, 1);
+    shutdown (hd->sock->fd, 1);
   hd->in_data = 0;
 
   /* Create a new cookie and a stream for reading.  */
   cookie = xtrycalloc (1, sizeof *cookie);
   if (!cookie)
-    return gpg_error_from_syserror ();
-  cookie->fd = hd->sock;
-  if (hd->uri->use_tls)
-    cookie->tls_session = hd->tls_context;
-  
+    return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+  cookie->sock = my_socket_ref (hd->sock);
+  cookie->session = http_session_ref (hd->session);
+  cookie->use_tls = hd->uri->use_tls;
+
   hd->read_cookie = cookie;
   hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
   if (!hd->fp_read)
     {
+      err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+      my_socket_unref (cookie->sock, NULL, NULL);
+      http_session_unref (cookie->session);
       xfree (cookie);
       hd->read_cookie = NULL;
-      return gpg_error_from_syserror ();
+      return err;
     }
 
   err = parse_response (hd);
+
+  if (!err)
+    err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd);
+
   return err;
 }
 
@@ -435,14 +923,15 @@ http_wait_response (http_t hd)
    be used as an HTTP proxy and any enabled $http_proxy gets
    ignored. */
 gpg_error_t
-http_open_document (http_t *r_hd, const char *document, 
+http_open_document (http_t *r_hd, const char *document,
                     const char *auth, unsigned int flags, const char *proxy,
-                    void *tls_context)
+                    http_session_t session,
+                    const char *srvtag, strlist_t headers)
 {
   gpg_error_t err;
 
-  err = http_open (r_hd, HTTP_REQ_GET, document, auth, flags,
-                   proxy, tls_context);
+  err = http_open (r_hd, HTTP_REQ_GET, document, NULL, auth, flags,
+                   proxy, session, srvtag, headers);
   if (err)
     return err;
 
@@ -459,12 +948,20 @@ http_close (http_t hd, int keep_read_stream)
 {
   if (!hd)
     return;
-  if (!hd->fp_read && !hd->fp_write && hd->sock != -1)
-    sock_close (hd->sock);
+
+  /* First remove the close notifications for the streams.  */
+  if (hd->fp_read)
+    es_onclose (hd->fp_read, 0, fp_onclose_notification, hd);
+  if (hd->fp_write)
+    es_onclose (hd->fp_write, 0, fp_onclose_notification, hd);
+
+  /* Now we can close the streams.  */
+  my_socket_unref (hd->sock, NULL, NULL);
   if (hd->fp_read && !keep_read_stream)
     es_fclose (hd->fp_read);
   if (hd->fp_write)
     es_fclose (hd->fp_write);
+  http_session_unref (hd->session);
   http_release_parsed_uri (hd->uri);
   while (hd->headers)
     {
@@ -496,21 +993,61 @@ http_get_status_code (http_t hd)
   return hd?hd->status_code:0;
 }
 
+/* Return information pertaining to TLS.  If TLS is not in use for HD,
+   NULL is returned.  WHAT is used ask for specific information:
+
+     (NULL) := Only check whether TLS is is use.  Returns an
+               unspecified string if TLS is in use.  That string may
+               even be the empty string.
+ */
+const char *
+http_get_tls_info (http_t hd, const char *what)
+{
+  (void)what;
+
+  if (!hd)
+    return NULL;
+
+  return hd->uri->use_tls? "":NULL;
+}
+
 
 \f
+static gpg_error_t
+parse_uri (parsed_uri_t *ret_uri, const char *uri,
+           int no_scheme_check, int force_tls)
+{
+  gpg_err_code_t ec;
+
+  *ret_uri = xtrycalloc (1, sizeof **ret_uri + strlen (uri));
+  if (!*ret_uri)
+    return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+  strcpy ((*ret_uri)->buffer, uri);
+  ec = do_parse_uri (*ret_uri, 0, no_scheme_check, force_tls);
+  if (ec)
+    {
+      xfree (*ret_uri);
+      *ret_uri = NULL;
+    }
+  return gpg_err_make (default_errsource, ec);
+}
+
+
 /*
  * Parse an URI and put the result into the newly allocated RET_URI.
- * The caller must always use release_parsed_uri() to releases the
- * resources (even on error).
+ * On success the caller must use http_release_parsed_uri() to
+ * releases the resources.  If NO_SCHEME_CHECK is set, the function
+ * tries to parse the URL in the same way it would do for an HTTP
+ * style URI.
  */
 gpg_error_t
-http_parse_uri (parsed_uri_t * ret_uri, const char *uri)
+http_parse_uri (parsed_uri_t *ret_uri, const char *uri,
+                int no_scheme_check)
 {
-  *ret_uri = xcalloc (1, sizeof **ret_uri + strlen (uri));
-  strcpy ((*ret_uri)->buffer, uri);
-  return do_parse_uri (*ret_uri, 0);
+  return parse_uri (ret_uri, uri, no_scheme_check, 0);
 }
 
+
 void
 http_release_parsed_uri (parsed_uri_t uri)
 {
@@ -528,8 +1065,9 @@ http_release_parsed_uri (parsed_uri_t uri)
 }
 
 
-static gpg_error_t
-do_parse_uri (parsed_uri_t uri, int only_local_part)
+static gpg_err_code_t
+do_parse_uri (parsed_uri_t uri, int only_local_part,
+              int no_scheme_check, int force_tls)
 {
   uri_tuple_t *tail;
   char *p, *p2, *p3, *pp;
@@ -543,42 +1081,52 @@ do_parse_uri (parsed_uri_t uri, int only_local_part)
   uri->port = 0;
   uri->params = uri->query = NULL;
   uri->use_tls = 0;
+  uri->is_http = 0;
+  uri->opaque = 0;
+  uri->v6lit = 0;
+  uri->onion = 0;
 
   /* A quick validity check. */
   if (strspn (p, VALID_URI_CHARS) != n)
-    return gpg_error (GPG_ERR_BAD_URI);        /* Invalid characters found. */
+    return GPG_ERR_BAD_URI;    /* Invalid characters found. */
 
   if (!only_local_part)
     {
       /* Find the scheme. */
       if (!(p2 = strchr (p, ':')) || p2 == p)
-       return gpg_error (GPG_ERR_BAD_URI); /* No scheme. */
+       return GPG_ERR_BAD_URI; /* No scheme. */
       *p2++ = 0;
       for (pp=p; *pp; pp++)
        *pp = tolower (*(unsigned char*)pp);
       uri->scheme = p;
-      if (!strcmp (uri->scheme, "http"))
-        uri->port = 80;
-#ifdef HTTP_USE_GNUTLS
-      else if (!strcmp (uri->scheme, "https"))
+      if (!strcmp (uri->scheme, "http") && !force_tls)
+        {
+          uri->port = 80;
+          uri->is_http = 1;
+        }
+      else if (!strcmp (uri->scheme, "hkp") && !force_tls)
+        {
+          uri->port = 11371;
+          uri->is_http = 1;
+        }
+#ifdef USE_TLS
+      else if (!strcmp (uri->scheme, "https") || !strcmp (uri->scheme,"hkps")
+               || (force_tls && (!strcmp (uri->scheme, "http")
+                                 || !strcmp (uri->scheme,"hkp"))))
         {
           uri->port = 443;
+          uri->is_http = 1;
           uri->use_tls = 1;
         }
-#endif
-      else
-       return gpg_error (GPG_ERR_INV_URI); /* Unsupported scheme */
+#endif /*USE_TLS*/
+      else if (!no_scheme_check)
+       return GPG_ERR_INV_URI; /* Unsupported scheme */
 
       p = p2;
 
-      /* Find the hostname */
-      if (*p != '/')
-       return gpg_error (GPG_ERR_INV_URI); /* Does not start with a slash. */
-
-      p++;
-      if (*p == '/') /* There seems to be a hostname. */
-       { 
-         p++;
+      if (*p == '/' && p[1] == '/' ) /* There seems to be a hostname. */
+       {
+          p += 2;
          if ((p2 = strchr (p, '/')))
            *p2++ = 0;
 
@@ -592,60 +1140,85 @@ do_parse_uri (parsed_uri_t uri, int only_local_part)
 
           for (pp=p; *pp; pp++)
             *pp = tolower (*(unsigned char*)pp);
-         uri->host = p;
+
+         /* Handle an IPv6 literal */
+         if( *p == '[' && (p3=strchr( p, ']' )) )
+           {
+             *p3++ = '\0';
+             /* worst case, uri->host should have length 0, points to \0 */
+             uri->host = p + 1;
+              uri->v6lit = 1;
+             p = p3;
+           }
+         else
+           uri->host = p;
+
          if ((p3 = strchr (p, ':')))
            {
-             *p3++ = 0;
+             *p3++ = '\0';
              uri->port = atoi (p3);
            }
 
-         uri->host = p;
          if ((n = remove_escapes (uri->host)) < 0)
-           return gpg_error (GPG_ERR_BAD_URI);
-         if (n != strlen (p))
-           return gpg_error (GPG_ERR_BAD_URI); /* Hostname incudes a Nul. */
+           return GPG_ERR_BAD_URI;
+         if (n != strlen (uri->host))
+           return GPG_ERR_BAD_URI;     /* Hostname incudes a Nul. */
          p = p2 ? p2 : NULL;
        }
-    } /* End global URI part. */
-
-  /* Parse the pathname part */
-  if (!p || !*p)
-    return 0;  /* We don't have a path.  Okay. */
-
-  /* TODO: Here we have to check params. */
-
-  /* Do we have a query part? */
-  if ((p2 = strchr (p, '?')))
-    *p2++ = 0;
-
-  uri->path = p;
-  if ((n = remove_escapes (p)) < 0)
-    return gpg_error (GPG_ERR_BAD_URI);
-  if (n != strlen (p))
-    return gpg_error (GPG_ERR_BAD_URI);        /* Path includes a Nul. */
-  p = p2 ? p2 : NULL;
+      else if (uri->is_http)
+       return GPG_ERR_INV_URI; /* No Leading double slash for HTTP.  */
+      else
+        {
+          uri->opaque = 1;
+          uri->path = p;
+          if (is_onion_address (uri->path))
+            uri->onion = 1;
+          return 0;
+        }
 
-  if (!p || !*p)       
-    return 0; /* We don't have a query string.  Okay. */
+    } /* End global URI part. */
 
-  /* Now parse the query string. */
-  tail = &uri->query;
-  for (;;)
+  /* Parse the pathname part if any.  */
+  if (p && *p)
     {
-      uri_tuple_t elem;
+      /* TODO: Here we have to check params. */
 
-      if ((p2 = strchr (p, '&')))
-       *p2++ = 0;
-      if (!(elem = parse_tuple (p)))
-       return gpg_error (GPG_ERR_BAD_URI);
-      *tail = elem;
-      tail = &elem->next;
+      /* Do we have a query part? */
+      if ((p2 = strchr (p, '?')))
+        *p2++ = 0;
 
-      if (!p2)
-       break; /* Ready. */
-      p = p2;
+      uri->path = p;
+      if ((n = remove_escapes (p)) < 0)
+        return GPG_ERR_BAD_URI;
+      if (n != strlen (p))
+        return GPG_ERR_BAD_URI;        /* Path includes a Nul. */
+      p = p2 ? p2 : NULL;
+
+      /* Parse a query string if any.  */
+      if (p && *p)
+        {
+          tail = &uri->query;
+          for (;;)
+            {
+              uri_tuple_t elem;
+
+              if ((p2 = strchr (p, '&')))
+                *p2++ = 0;
+              if (!(elem = parse_tuple (p)))
+                return GPG_ERR_BAD_URI;
+              *tail = elem;
+              tail = &elem->next;
+
+              if (!p2)
+                break; /* Ready. */
+              p = p2;
+            }
+        }
     }
 
+  if (is_onion_address (uri->host))
+    uri->onion = 1;
+
   return 0;
 }
 
@@ -699,16 +1272,41 @@ remove_escapes (char *string)
 }
 
 
-static int
-insert_escapes (char *buffer, const char *string,
-               const char *special)
+/* If SPECIAL is NULL this function escapes in forms mode.  */
+static size_t
+escape_data (char *buffer, const void *data, size_t datalen,
+             const char *special)
 {
-  const unsigned char *s = (const unsigned char*)string;
-  int n = 0;
+  int forms = !special;
+  const unsigned char *s;
+  size_t n = 0;
+
+  if (forms)
+    special = "%;?&=";
 
-  for (; *s; s++)
+  for (s = data; datalen; s++, datalen--)
     {
-      if (strchr (VALID_URI_CHARS, *s) && !strchr (special, *s))
+      if (forms && *s == ' ')
+        {
+         if (buffer)
+           *buffer++ = '+';
+         n++;
+        }
+      else if (forms && *s == '\n')
+        {
+         if (buffer)
+           memcpy (buffer, "%0D%0A", 6);
+         n += 6;
+        }
+      else if (forms && *s == '\r' && datalen > 1 && s[1] == '\n')
+        {
+         if (buffer)
+           memcpy (buffer, "%0D%0A", 6);
+         n += 6;
+          s++;
+          datalen--;
+        }
+      else if (strchr (VALID_URI_CHARS, *s) && !strchr (special, *s))
        {
          if (buffer)
            *(unsigned char*)buffer++ = *s;
@@ -718,7 +1316,7 @@ insert_escapes (char *buffer, const char *string,
        {
          if (buffer)
            {
-             sprintf (buffer, "%%%02X", *s);
+             snprintf (buffer, 4, "%%%02X", *s);
              buffer += 3;
            }
          n += 3;
@@ -728,11 +1326,20 @@ insert_escapes (char *buffer, const char *string,
 }
 
 
+static int
+insert_escapes (char *buffer, const char *string,
+               const char *special)
+{
+  return escape_data (buffer, string, strlen (string), special);
+}
+
+
 /* Allocate a new string from STRING using standard HTTP escaping as
    well as escaping of characters given in SPECIALS.  A common pattern
    for SPECIALS is "%;?&=". However it depends on the needs, for
    example "+" and "/: often needs to be escaped too.  Returns NULL on
-   failure and sets ERRNO. */
+   failure and sets ERRNO.  If SPECIAL is NULL a dedicated forms
+   encoding mode is used. */
 char *
 http_escape_string (const char *string, const char *specials)
 {
@@ -749,6 +1356,27 @@ http_escape_string (const char *string, const char *specials)
   return buf;
 }
 
+/* Allocate a new string from {DATA,DATALEN} using standard HTTP
+   escaping as well as escaping of characters given in SPECIALS.  A
+   common pattern for SPECIALS is "%;?&=".  However it depends on the
+   needs, for example "+" and "/: often needs to be escaped too.
+   Returns NULL on failure and sets ERRNO.  If SPECIAL is NULL a
+   dedicated forms encoding mode is used. */
+char *
+http_escape_data (const void *data, size_t datalen, const char *specials)
+{
+  int n;
+  char *buf;
+
+  n = escape_data (NULL, data, datalen, specials);
+  buf = xtrymalloc (n+1);
+  if (buf)
+    {
+      escape_data (buf, data, datalen, specials);
+      buf[n] = 0;
+    }
+  return buf;
+}
 
 
 static uri_tuple_t
@@ -789,14 +1417,41 @@ parse_tuple (char *string)
 }
 
 
+/* Return true if STRING is likely "hostname:port" or only "hostname".  */
+static int
+is_hostname_port (const char *string)
+{
+  int colons = 0;
+
+  if (!string || !*string)
+    return 0;
+  for (; *string; string++)
+    {
+      if (*string == ':')
+        {
+          if (colons)
+            return 0;
+          if (!string[1])
+            return 0;
+          colons++;
+        }
+      else if (!colons && strchr (" \t\f\n\v_@[]/", *string))
+        return 0; /* Invalid characters in hostname. */
+      else if (colons && !digitp (string))
+        return 0; /* Not a digit in the port.  */
+    }
+  return 1;
+}
+
+
 /*
  * Send a HTTP request to the server
  * Returns 0 if the request was successful
  */
 static gpg_error_t
-send_request (http_t hd, const char *auth, const char *proxy)
+send_request (http_t hd, const char *httphost, const char *auth,
+             const char *proxy, const char *srvtag, strlist_t headers)
 {
-  gnutls_session_t tls_session;
   gpg_error_t err;
   const char *server;
   char *request, *p;
@@ -804,38 +1459,107 @@ send_request (http_t hd, const char *auth, const char *proxy)
   const char *http_proxy = NULL;
   char *proxy_authstr = NULL;
   char *authstr = NULL;
-  int save_errno;
-  cookie_t cookie;
+  int sock;
+  int hnf;
 
+  if (hd->uri->use_tls && !hd->session)
+    {
+      log_error ("TLS requested but no session object provided\n");
+      return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
+    }
+#ifdef USE_TLS
+  if (hd->uri->use_tls && !hd->session->tls_session)
+    {
+      log_error ("TLS requested but no GNUTLS context available\n");
+      return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
+    }
+#endif /*USE_TLS*/
+
+  if ((hd->flags & HTTP_FLAG_FORCE_TOR))
+    {
+      int mode;
+
+      if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
+        {
+          log_error ("Tor support is not available\n");
+          return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
+        }
+    }
+
+  server = *hd->uri->host ? hd->uri->host : "localhost";
+  port = hd->uri->port ? hd->uri->port : 80;
+
+  /* Try to use SNI.  */
+#ifdef USE_TLS
+  if (hd->uri->use_tls)
+    {
+# if HTTP_USE_GNUTLS
+      int rc;
+# endif
+
+      xfree (hd->session->servername);
+      hd->session->servername = xtrystrdup (httphost? httphost : server);
+      if (!hd->session->servername)
+        {
+          err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+          return err;
+        }
 
-  tls_session = hd->tls_context;
-  if (hd->uri->use_tls && !tls_session)
-    {
-      log_error ("TLS requested but no GNUTLS context provided\n");
-      return gpg_error (GPG_ERR_INTERNAL);
+# if HTTP_USE_NTBTLS
+      err = ntbtls_set_hostname (hd->session->tls_session,
+                                 hd->session->servername);
+      if (err)
+        {
+          log_info ("ntbtls_set_hostname failed: %s\n", gpg_strerror (err));
+          return err;
+        }
+# elif HTTP_USE_GNUTLS
+      rc = gnutls_server_name_set (hd->session->tls_session,
+                                   GNUTLS_NAME_DNS,
+                                   hd->session->servername,
+                                   strlen (hd->session->servername));
+      if (rc < 0)
+        log_info ("gnutls_server_name_set failed: %s\n", gnutls_strerror (rc));
+# endif /*HTTP_USE_GNUTLS*/
     }
-
-  server = *hd->uri->host ? hd->uri->host : "localhost";
-  port = hd->uri->port ? hd->uri->port : 80;
+#endif /*USE_TLS*/
 
   if ( (proxy && *proxy)
        || ( (hd->flags & HTTP_FLAG_TRY_PROXY)
-            && (http_proxy = getenv (HTTP_PROXY_ENV)) 
+            && (http_proxy = getenv (HTTP_PROXY_ENV))
             && *http_proxy ))
     {
       parsed_uri_t uri;
+      int save_errno;
 
       if (proxy)
        http_proxy = proxy;
 
-      err = http_parse_uri (&uri, http_proxy);
+      err = parse_uri (&uri, http_proxy, 0, 0);
+      if (gpg_err_code (err) == GPG_ERR_INV_URI
+          && is_hostname_port (http_proxy))
+        {
+          /* Retry assuming a "hostname:port" string.  */
+          char *tmpname = strconcat ("http://", http_proxy, NULL);
+          if (tmpname && !parse_uri (&uri, tmpname, 0, 0))
+            err = 0;
+          xfree (tmpname);
+        }
+
+      if (err)
+        ;
+      else if (!strcmp (uri->scheme, "http") || !strcmp (uri->scheme, "socks4"))
+        ;
+      else if (!strcmp (uri->scheme, "socks5h"))
+        err = gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
+      else
+        err = gpg_err_make (default_errsource, GPG_ERR_INV_URI);
+
       if (err)
        {
          log_error ("invalid HTTP proxy (%s): %s\n",
                     http_proxy, gpg_strerror (err));
-         http_release_parsed_uri (uri);
-         return gpg_error (GPG_ERR_CONFIGURATION);
-
+         return gpg_err_make (default_errsource, GPG_ERR_CONFIGURATION);
        }
 
       if (uri->auth)
@@ -846,60 +1570,121 @@ send_request (http_t hd, const char *auth, const char *proxy)
                                             uri->auth, strlen(uri->auth));
           if (!proxy_authstr)
             {
-              err = gpg_error_from_syserror ();
+              err = gpg_err_make (default_errsource,
+                                  gpg_err_code_from_syserror ());
               http_release_parsed_uri (uri);
               return err;
             }
         }
 
-      hd->sock = connect_server (*uri->host ? uri->host : "localhost",
-                                uri->port ? uri->port : 80,
-                                 hd->flags, hd->uri->scheme);
+      sock = connect_server (*uri->host ? uri->host : "localhost",
+                             uri->port ? uri->port : 80,
+                             hd->flags, srvtag, &hnf);
       save_errno = errno;
       http_release_parsed_uri (uri);
+      if (sock == ASSUAN_INVALID_FD)
+        gpg_err_set_errno (save_errno);
     }
   else
     {
-      hd->sock = connect_server (server, port, hd->flags, hd->uri->scheme);
-      save_errno = errno;
+      sock = connect_server (server, port, hd->flags, srvtag, &hnf);
     }
 
-  if (hd->sock == -1)
+  if (sock == ASSUAN_INVALID_FD)
+    {
+      xfree (proxy_authstr);
+      return gpg_err_make (default_errsource,
+                           (hnf? GPG_ERR_UNKNOWN_HOST
+                               : gpg_err_code_from_syserror ()));
+    }
+  hd->sock = my_socket_new (sock);
+  if (!hd->sock)
     {
       xfree (proxy_authstr);
-      return (save_errno 
-              ? gpg_error_from_errno (save_errno)
-              : gpg_error (GPG_ERR_NOT_FOUND));
+      return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
     }
 
-#ifdef HTTP_USE_GNUTLS
+
+
+#if HTTP_USE_NTBTLS
+  if (hd->uri->use_tls)
+    {
+      my_socket_ref (hd->sock);
+
+      while ((err = ntbtls_handshake (hd->session->tls_session)))
+        {
+          switch (err)
+            {
+            default:
+              log_info ("TLS handshake failed: %s <%s>\n",
+                        gpg_strerror (err), gpg_strsource (err));
+              xfree (proxy_authstr);
+              return err;
+            }
+        }
+
+      hd->session->verify.done = 0;
+      if (tls_callback)
+        err = tls_callback (hd, hd->session, 0);
+      else
+        err = http_verify_server_credentials (hd->session);
+      if (err)
+        {
+          log_info ("TLS connection authentication failed: %s <%s>\n",
+                    gpg_strerror (err), gpg_strsource (err));
+          xfree (proxy_authstr);
+          return err;
+        }
+    }
+#elif HTTP_USE_GNUTLS
   if (hd->uri->use_tls)
     {
       int rc;
 
-      gnutls_transport_set_ptr (tls_session, (gnutls_transport_ptr_t)hd->sock);
+      my_socket_ref (hd->sock);
+      gnutls_transport_set_ptr (hd->session->tls_session, hd->sock);
+      gnutls_transport_set_pull_function (hd->session->tls_session,
+                                          my_gnutls_read);
+      gnutls_transport_set_push_function (hd->session->tls_session,
+                                          my_gnutls_write);
+
       do
         {
-          rc = gnutls_handshake (tls_session);
+          rc = gnutls_handshake (hd->session->tls_session);
         }
       while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN);
       if (rc < 0)
         {
-          log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc));
+          if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED
+              || rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
+            {
+              gnutls_alert_description_t alertno;
+              const char *alertstr;
+
+              alertno = gnutls_alert_get (hd->session->tls_session);
+              alertstr = gnutls_alert_get_name (alertno);
+              log_info ("TLS handshake failed: %s (alert %d)\n",
+                        alertstr, (int)alertno);
+              if (alertno == GNUTLS_A_UNRECOGNIZED_NAME && server)
+                log_info ("  (sent server name '%s')\n", server);
+            }
+          else
+            log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc));
           xfree (proxy_authstr);
-          return gpg_error (GPG_ERR_NETWORK);
+          return gpg_err_make (default_errsource, GPG_ERR_NETWORK);
         }
 
+      hd->session->verify.done = 0;
       if (tls_callback)
+        err = tls_callback (hd, hd->session, 0);
+      else
+        err = http_verify_server_credentials (hd->session);
+      if (err)
         {
-          err = tls_callback (hd, tls_session, 0);
-          if (err)
-            {
-              log_info ("TLS connection authentication failed: %s\n",
-                        gpg_strerror (err));
-              xfree (proxy_authstr);
-              return err;
-            }
+          log_info ("TLS connection authentication failed: %s\n",
+                    gpg_strerror (err));
+          xfree (proxy_authstr);
+          return err;
         }
     }
 #endif /*HTTP_USE_GNUTLS*/
@@ -907,14 +1692,14 @@ send_request (http_t hd, const char *auth, const char *proxy)
   if (auth || hd->uri->auth)
     {
       char *myauth;
-      
+
       if (auth)
         {
           myauth = xtrystrdup (auth);
           if (!myauth)
             {
               xfree (proxy_authstr);
-              return gpg_error_from_syserror ();
+              return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
             }
           remove_escapes (myauth);
         }
@@ -924,7 +1709,7 @@ send_request (http_t hd, const char *auth, const char *proxy)
           myauth = hd->uri->auth;
         }
 
-      authstr = make_header_line ("Authorization: Basic %s", "\r\n",
+      authstr = make_header_line ("Authorization: Basic ", "\r\n",
                                   myauth, strlen (myauth));
       if (auth)
         xfree (myauth);
@@ -932,74 +1717,101 @@ send_request (http_t hd, const char *auth, const char *proxy)
       if (!authstr)
         {
           xfree (proxy_authstr);
-          return gpg_error_from_syserror ();
+          return gpg_err_make (default_errsource,
+                               gpg_err_code_from_syserror ());
         }
     }
-  
+
   p = build_rel_path (hd->uri);
   if (!p)
-    return gpg_error_from_syserror ();
+    return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
 
   if (http_proxy && *http_proxy)
     {
-      request = es_asprintf 
-        ("%s http://%s:%hu%s%s HTTP/1.0\r\n%s%s",
+      request = es_bsprintf
+        ("%s %s://%s:%hu%s%s HTTP/1.0\r\n%s%s",
          hd->req_type == HTTP_REQ_GET ? "GET" :
          hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
          hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
-         server, port, *p == '/' ? "" : "/", p,
+         hd->uri->use_tls? "https" : "http",
+         httphost? httphost : server,
+         port, *p == '/' ? "" : "/", p,
          authstr ? authstr : "",
          proxy_authstr ? proxy_authstr : "");
     }
   else
     {
       char portstr[35];
-        
+
       if (port == 80)
         *portstr = 0;
       else
         snprintf (portstr, sizeof portstr, ":%u", port);
 
-      request = es_asprintf 
+      request = es_bsprintf
         ("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s",
          hd->req_type == HTTP_REQ_GET ? "GET" :
          hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
          hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
-         *p == '/' ? "" : "/", p, server, portstr,
+         *p == '/' ? "" : "/", p,
+         httphost? httphost : server,
+         portstr,
          authstr? authstr:"");
     }
   xfree (p);
   if (!request)
     {
-      err = gpg_error_from_syserror ();
+      err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
       xfree (authstr);
       xfree (proxy_authstr);
       return err;
     }
 
+  /* log_debug ("request:\n%s\nEND request\n", request); */
+
   /* First setup estream so that we can write even the first line
      using estream.  This is also required for the sake of gnutls. */
-  cookie = xtrycalloc (1, sizeof *cookie);
-  if (!cookie)
-    {
-      err = gpg_error_from_syserror ();
-      goto leave;
-    }
-  cookie->fd = hd->sock;
-  hd->write_cookie = cookie;
-  if (hd->uri->use_tls)
-    cookie->tls_session = tls_session;
-  hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
-  if (!hd->fp_write)
+  {
+    cookie_t cookie;
+
+    cookie = xtrycalloc (1, sizeof *cookie);
+    if (!cookie)
+      {
+        err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+        goto leave;
+      }
+    cookie->sock = my_socket_ref (hd->sock);
+    hd->write_cookie = cookie;
+    cookie->use_tls = hd->uri->use_tls;
+    cookie->session = http_session_ref (hd->session);
+
+    hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
+    if (!hd->fp_write)
+      {
+        err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+        my_socket_unref (cookie->sock, NULL, NULL);
+        xfree (cookie);
+        hd->write_cookie = NULL;
+      }
+    else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
+      err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+    else
+      err = 0;
+
+  if (!err)
     {
-      xfree (cookie);
-      hd->write_cookie = NULL;
-      err = gpg_error_from_syserror ();
+      for (;headers; headers=headers->next)
+        {
+          if ((es_fputs (headers->d, hd->fp_write) || es_fflush (hd->fp_write))
+              || (es_fputs("\r\n",hd->fp_write) || es_fflush(hd->fp_write)))
+            {
+              err = gpg_err_make (default_errsource,
+                                  gpg_err_code_from_syserror ());
+              break;
+            }
+        }
     }
-  else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
-    err = gpg_error_from_syserror ();
-  else
-    err = 0;
+  }
 
  leave:
   es_free (request);
@@ -1089,7 +1901,7 @@ capitalize_header_name (char *name)
 /* Store an HTTP header line in LINE away.  Line continuation is
    supported as well as merging of headers with the same name. This
    function may modify LINE. */
-static gpg_error_t
+static gpg_err_code_t
 store_header (http_t hd, char *line)
 {
   size_t n;
@@ -1104,17 +1916,17 @@ store_header (http_t hd, char *line)
         line[--n] = 0;
     }
   if (!n)  /* we are never called to hit this. */
-    return gpg_error (GPG_ERR_BUG);
+    return GPG_ERR_BUG;
   if (*line == ' ' || *line == '\t')
     {
       /* Continuation. This won't happen too often as it is not
          recommended.  We use a straightforward implementaion. */
       if (!hd->headers)
-        return gpg_error (GPG_ERR_PROTOCOL_VIOLATION);
+        return GPG_ERR_PROTOCOL_VIOLATION;
       n += strlen (hd->headers->value);
       p = xtrymalloc (n+1);
       if (!p)
-        return gpg_error_from_syserror ();
+        return gpg_err_code_from_syserror ();
       strcpy (stpcpy (p, hd->headers->value), line);
       xfree (hd->headers->value);
       hd->headers->value = p;
@@ -1124,12 +1936,12 @@ store_header (http_t hd, char *line)
   capitalize_header_name (line);
   p = strchr (line, ':');
   if (!p)
-    return gpg_error (GPG_ERR_PROTOCOL_VIOLATION);
+    return GPG_ERR_PROTOCOL_VIOLATION;
   *p++ = 0;
   while (*p == ' ' || *p == '\t')
     p++;
   value = p;
-  
+
   for (h=hd->headers; h; h = h->next)
     if ( !strcmp (h->name, line) )
       break;
@@ -1139,7 +1951,7 @@ store_header (http_t hd, char *line)
          it is a comma separated list and merge them.  */
       p = xtrymalloc (strlen (h->value) + 1 + strlen (value)+ 1);
       if (!p)
-        return gpg_error_from_syserror ();
+        return gpg_err_code_from_syserror ();
       strcpy (stpcpy (stpcpy (p, h->value), ","), value);
       xfree (h->value);
       h->value = p;
@@ -1149,13 +1961,13 @@ store_header (http_t hd, char *line)
   /* Append a new header. */
   h = xtrymalloc (sizeof *h + strlen (line));
   if (!h)
-    return gpg_error_from_syserror ();
+    return gpg_err_code_from_syserror ();
   strcpy (h->name, line);
   h->value = xtrymalloc (strlen (value)+1);
   if (!h->value)
     {
       xfree (h);
-      return gpg_error_from_syserror ();
+      return gpg_err_code_from_syserror ();
     }
   strcpy (h->value, value);
   h->next = hd->headers;
@@ -1166,12 +1978,10 @@ store_header (http_t hd, char *line)
 
 
 /* Return the header NAME from the last response.  The returned value
-   is valid as along as HD has not been closed and no othe request has
-   been send. If the header was not found, NULL is returned.  Name
+   is valid as along as HD has not been closed and no other request
+   has been send. If the header was not found, NULL is returned.  NAME
    must be canonicalized, that is the first letter of each dash
-   delimited part must be uppercase and all other letters lowercase.
-   Note that the context must have been opened with the
-   HTTP_FLAG_NEED_HEADER. */
+   delimited part must be uppercase and all other letters lowercase.  */
 const char *
 http_get_header (http_t hd, const char *name)
 {
@@ -1184,12 +1994,35 @@ http_get_header (http_t hd, const char *name)
 }
 
 
+/* Return a newly allocated and NULL terminated array with pointers to
+   header names.  The array must be released with xfree() and its
+   content is only values as long as no other request has been
+   send.  */
+const char **
+http_get_header_names (http_t hd)
+{
+  const char **array;
+  size_t n;
+  header_t h;
+
+  for (n=0, h = hd->headers; h; h = h->next)
+    n++;
+  array = xtrycalloc (n+1, sizeof *array);
+  if (array)
+    {
+      for (n=0, h = hd->headers; h; h = h->next)
+        array[n++] = h->name;
+    }
+
+  return array;
+}
+
 
 /*
  * Parse the response from a server.
  * Returns: Errorcode and sets some files in the handle
  */
-static gpg_error_t
+static gpg_err_code_t
 parse_response (http_t hd)
 {
   char *line, *p, *p2;
@@ -1213,13 +2046,14 @@ parse_response (http_t hd)
       len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
       line = hd->buffer;
       if (!line)
-       return gpg_error_from_syserror (); /* Out of core. */
+       return gpg_err_code_from_syserror (); /* Out of core. */
       if (!maxlen)
-       return gpg_error (GPG_ERR_TRUNCATED); /* Line has been truncated. */
+       return GPG_ERR_TRUNCATED; /* Line has been truncated. */
       if (!len)
-       return gpg_error (GPG_ERR_EOF);
-      if ( (hd->flags & HTTP_FLAG_LOG_RESP) )
-        log_info ("RESP: `%.*s'\n",
+       return GPG_ERR_EOF;
+
+      if ((hd->flags & HTTP_FLAG_LOG_RESP))
+        log_info ("RESP: '%.*s'\n",
                   (int)strlen(line)-(*line&&line[1]?2:0),line);
     }
   while (!*line);
@@ -1257,21 +2091,21 @@ parse_response (http_t hd)
       len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
       line = hd->buffer;
       if (!line)
-       return gpg_error_from_syserror (); /* Out of core. */
+       return gpg_err_code_from_syserror (); /* Out of core. */
       /* Note, that we can silently ignore truncated lines. */
       if (!len)
-       return gpg_error (GPG_ERR_EOF);
+       return GPG_ERR_EOF;
       /* Trim line endings of empty lines. */
       if ((*line == '\r' && line[1] == '\n') || *line == '\n')
        *line = 0;
-      if ( (hd->flags & HTTP_FLAG_LOG_RESP) )
-        log_info ("RESP: `%.*s'\n",
+      if ((hd->flags & HTTP_FLAG_LOG_RESP))
+        log_info ("RESP: '%.*s'\n",
                   (int)strlen(line)-(*line&&line[1]?2:0),line);
-      if ( (hd->flags & HTTP_FLAG_NEED_HEADER) && *line )
+      if (*line)
         {
-          gpg_error_t err = store_header (hd, line);
-          if (err)
-            return err;
+          gpg_err_code_t ec = store_header (hd, line);
+          if (ec)
+            return ec;
         }
     }
   while (len && *line);
@@ -1333,14 +2167,14 @@ start_server ()
       FD_ZERO (&rfds);
       FD_SET (fd, &rfds);
 
-      if (select (fd + 1, &rfds, NULL, NULL, NULL) <= 0)
+      if (my_select (fd + 1, &rfds, NULL, NULL, NULL) <= 0)
        continue;               /* ignore any errors */
 
       if (!FD_ISSET (fd, &rfds))
        continue;
 
       addrlen = sizeof peer;
-      client = accept (fd, (struct sockaddr *) &peer, &addrlen);
+      client = my_accept (fd, (struct sockaddr *) &peer, &addrlen);
       if (client == -1)
        continue;               /* oops */
 
@@ -1367,70 +2201,78 @@ start_server ()
 }
 #endif
 
-/* Actually connect to a server.  Returns the file descripto or -1 on
+/* Actually connect to a server.  Returns the file descriptor or -1 on
    error.  ERRNO is set on error. */
-static int
+static assuan_fd_t
 connect_server (const char *server, unsigned short port,
-                unsigned int flags, const char *srvtag)
+                unsigned int flags, const char *srvtag, int *r_host_not_found)
 {
-  int sock = -1;
+  gpg_error_t err;
+  assuan_fd_t sock = ASSUAN_INVALID_FD;
   int srvcount = 0;
   int hostfound = 0;
+  int anyhostaddr = 0;
   int srv, connected;
   int last_errno = 0;
   struct srventry *serverlist = NULL;
+  int ret;
 
-#ifdef HAVE_W32_SYSTEM
-  unsigned long inaddr;
-
-#ifndef HTTP_NO_WSASTARTUP
+  *r_host_not_found = 0;
+#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
   init_sockets ();
-#endif
-  /* Win32 gethostbyname doesn't handle IP addresses internally, so we
-     try inet_addr first on that platform only. */
-  inaddr = inet_addr(server);
-  if ( inaddr != INADDR_NONE )
-    {
-      struct sockaddr_in addr;
-      
-      memset(&addr,0,sizeof(addr));
-      
-      sock = socket(AF_INET,SOCK_STREAM,0);
-      if ( sock==INVALID_SOCKET )
-       {
-         log_error("error creating socket: ec=%d\n",(int)WSAGetLastError());
-         return -1;
-       }
+#endif /*Windows*/
 
-      addr.sin_family = AF_INET; 
-      addr.sin_port = htons(port);
-      memcpy (&addr.sin_addr,&inaddr,sizeof(inaddr));      
+  /* Onion addresses require special treatment.  */
+  if (is_onion_address (server))
+    {
+#ifdef ASSUAN_SOCK_TOR
 
-      if (!connect (sock,(struct sockaddr *)&addr,sizeof(addr)) )
-       return sock;
-      sock_close(sock);
-      return -1;
+      sock = assuan_sock_connect_byname (server, port, 0, NULL,
+                                         ASSUAN_SOCK_TOR);
+      if (sock == ASSUAN_INVALID_FD)
+        {
+          if (errno == EHOSTUNREACH)
+            *r_host_not_found = 1;
+          log_error ("can't connect to '%s': %s\n", server, strerror (errno));
+        }
+      return sock;
+
+#else /*!ASSUAN_SOCK_TOR*/
+
+      gpg_err_set_errno (ENETUNREACH);
+      return -1; /* Out of core.  */
+
+#endif /*!HASSUAN_SOCK_TOR*/
     }
-#endif /*HAVE_W32_SYSTEM*/
 
 #ifdef USE_DNS_SRV
   /* Do the SRV thing */
-  if ((flags & HTTP_FLAG_TRY_SRV) && srvtag)
+  if (srvtag)
     {
       /* We're using SRV, so append the tags. */
-      if (1+strlen (srvtag) + 6 + strlen (server) + 1 <= MAXDNAME)
+      if (1 + strlen (srvtag) + 6 + strlen (server) + 1
+          <= DIMof (struct srventry, target))
        {
-         char srvname[MAXDNAME];
+         char *srvname = xtrymalloc (DIMof (struct srventry, target));
 
-         stpcpy (stpcpy (stpcpy (stpcpy (srvname,"_"), srvtag),
-                           "._tcp."), server);
-         srvcount = getsrv (srvname, &serverlist);
+          if (!srvname) /* Out of core */
+            {
+              serverlist = NULL;
+              srvcount = 0;
+            }
+          else
+            {
+              stpcpy (stpcpy (stpcpy (stpcpy (srvname,"_"), srvtag),
+                              "._tcp."), server);
+              srvcount = getsrv (srvname, &serverlist);
+              xfree (srvname);
+            }
        }
     }
-#else /*!USE_DNS_SRV*/
+#else
   (void)flags;
   (void)srvtag;
-#endif /*!USE_DNS_SRV*/
+#endif /*USE_DNS_SRV*/
 
   if (!serverlist)
     {
@@ -1440,132 +2282,143 @@ connect_server (const char *server, unsigned short port,
       if (!serverlist)
         return -1; /* Out of core.  */
       serverlist->port = port;
-      strncpy (serverlist->target, server, MAXDNAME);
-      serverlist->target[MAXDNAME-1] = '\0';
+      strncpy (serverlist->target, server, DIMof (struct srventry, target));
+      serverlist->target[DIMof (struct srventry, target)-1] = '\0';
       srvcount = 1;
     }
 
-#ifdef HAVE_GETADDRINFO
   connected = 0;
   for (srv=0; srv < srvcount && !connected; srv++)
     {
-      struct addrinfo hints, *res, *ai;
-      char portstr[35];
+      dns_addrinfo_t aibuf, ai;
 
-      sprintf (portstr, "%hu", port);
-      memset (&hints, 0, sizeof (hints));
-      hints.ai_socktype = SOCK_STREAM;
-      if (getaddrinfo (serverlist[srv].target, portstr, &hints, &res))
-        continue; /* Not found - try next one. */
+      err = resolve_dns_name (serverlist[srv].target, port, 0, SOCK_STREAM,
+                              &aibuf, NULL);
+      if (err)
+        {
+          log_info ("resolving '%s' failed: %s\n",
+                    serverlist[srv].target, gpg_strerror (err));
+          continue; /* Not found - try next one. */
+        }
       hostfound = 1;
 
-      for (ai = res; ai && !connected; ai = ai->ai_next)
+      for (ai = aibuf; ai && !connected; ai = ai->next)
         {
-          if (sock != -1)
-            sock_close (sock);
-          sock = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol);
-          if (sock == -1)
+          if (ai->family == AF_INET && (flags & HTTP_FLAG_IGNORE_IPv4))
+            continue;
+          if (ai->family == AF_INET6 && (flags & HTTP_FLAG_IGNORE_IPv6))
+            continue;
+
+          if (sock != ASSUAN_INVALID_FD)
+            assuan_sock_close (sock);
+          sock = assuan_sock_new (ai->family, ai->socktype, ai->protocol);
+          if (sock == ASSUAN_INVALID_FD)
             {
               int save_errno = errno;
               log_error ("error creating socket: %s\n", strerror (errno));
-              freeaddrinfo (res);
+              free_dns_addrinfo (aibuf);
               xfree (serverlist);
               errno = save_errno;
-              return -1;
+              return ASSUAN_INVALID_FD;
             }
-          
-          if (connect (sock, ai->ai_addr, ai->ai_addrlen))
-            last_errno = errno;
-          else
-            connected = 1;
-        }
-      freeaddrinfo (res);
-    }
-#else /* !HAVE_GETADDRINFO */
-  connected = 0;
-  for (srv=0; srv < srvcount && !connected; srv++)
-    {
-      int i;
-      struct hostent *host = NULL;
-      struct sockaddr_in addr;
-
-      /* Note: This code is not thread-safe.  */
-
-      memset (&addr, 0, sizeof (addr));
-      host = gethostbyname (serverlist[srv].target);
-      if (!host)
-        continue;
-      hostfound = 1;
-
-      if (sock != -1)
-        sock_close (sock);
-      sock = socket (host->h_addrtype, SOCK_STREAM, 0);
-      if (sock == -1)
-        {
-          log_error (_("error creating socket: %s\n"), strerror (errno));
-          xfree (serverlist);
-          return -1;
-        }
-      
-      addr.sin_family = host->h_addrtype;
-      if (addr.sin_family != AF_INET)
-       {
-         log_error ("unknown address family for `%s'\n",
-                     serverlist[srv].target);
-          xfree (serverlist);
-         return -1;
-       }
-      addr.sin_port = htons (serverlist[srv].port);
-      if (host->h_length != 4)
-        {
-          log_error ("illegal address length for `%s'\n",
-                     serverlist[srv].target);
-          xfree (serverlist);
-          return -1;
-        }
 
-      /* Try all A records until one responds. */
-      for (i = 0; host->h_addr_list[i] && !connected; i++)
-        {
-          memcpy (&addr.sin_addr, host->h_addr_list[i], host->h_length);
-          if (connect (sock, (struct sockaddr *) &addr, sizeof (addr)))
+          anyhostaddr = 1;
+          ret = assuan_sock_connect (sock, ai->addr, ai->addrlen);
+          if (ret)
             last_errno = errno;
           else
-            {
-              connected = 1;
-              break;
-            }
+            connected = 1;
         }
+      free_dns_addrinfo (aibuf);
     }
-#endif /* !HAVE_GETADDRINFO */
 
   xfree (serverlist);
 
   if (!connected)
     {
+      if (!hostfound)
+        log_error ("can't connect to '%s': %s\n",
+                   server, "host not found");
+      else if (!anyhostaddr)
+        log_error ("can't connect to '%s': %s\n",
+                   server, "no IP address for host");
+      else
+        {
 #ifdef HAVE_W32_SYSTEM
-      log_error ("can't connect to `%s': %s%sec=%d\n",
-                   server,
-                   hostfound? "":_("host not found"),
-                   hostfound? "":" - ", (int)WSAGetLastError());
+        log_error ("can't connect to '%s': ec=%d\n",
+                   server, (int)WSAGetLastError());
 #else
-      log_error ("can't connect to `%s': %s\n",
-                 server,
-                 hostfound? strerror (last_errno):"host not found");
+        log_error ("can't connect to '%s': %s\n",
+                   server, strerror (last_errno));
 #endif
-      if (sock != -1)
-       sock_close (sock);
-      errno = last_errno;
-      return -1;
+        }
+      if (!hostfound || (hostfound && !anyhostaddr))
+        *r_host_not_found = 1;
+      if (sock != ASSUAN_INVALID_FD)
+       assuan_sock_close (sock);
+      gpg_err_set_errno (last_errno);
+      return ASSUAN_INVALID_FD;
     }
   return sock;
 }
 
 
+static gpg_error_t
+write_server (int sock, const char *data, size_t length)
+{
+  int nleft;
+  int nwritten;
+
+  nleft = length;
+  while (nleft > 0)
+    {
+#if defined(HAVE_W32_SYSTEM)
+# if defined(USE_NPTH)
+      npth_unprotect ();
+# endif
+      nwritten = send (sock, data, nleft, 0);
+# if defined(USE_NPTH)
+      npth_protect ();
+# endif
+      if ( nwritten == SOCKET_ERROR )
+        {
+          log_info ("network write failed: ec=%d\n", (int)WSAGetLastError ());
+          return gpg_error (GPG_ERR_NETWORK);
+        }
+#else /*!HAVE_W32_SYSTEM*/
+# ifdef USE_NPTH
+      nwritten = npth_write (sock, data, nleft);
+# else
+      nwritten = write (sock, data, nleft);
+# endif
+      if (nwritten == -1)
+       {
+         if (errno == EINTR)
+           continue;
+         if (errno == EAGAIN)
+           {
+             struct timeval tv;
+
+             tv.tv_sec = 0;
+             tv.tv_usec = 50000;
+             my_select (0, NULL, NULL, NULL, &tv);
+             continue;
+           }
+         log_info ("network write failed: %s\n", strerror (errno));
+         return gpg_error_from_syserror ();
+       }
+#endif /*!HAVE_W32_SYSTEM*/
+      nleft -= nwritten;
+      data += nwritten;
+    }
+
+  return 0;
+}
+
 
 \f
 /* Read handler for estream.  */
-static ssize_t
+static gpgrt_ssize_t
 cookie_read (void *cookie, void *buffer, size_t size)
 {
   cookie_t c = cookie;
@@ -1580,10 +2433,10 @@ cookie_read (void *cookie, void *buffer, size_t size)
     }
 
 #ifdef HTTP_USE_GNUTLS
-  if (c->tls_session)
+  if (c->use_tls && c->session && c->session->tls_session)
     {
     again:
-      nread = gnutls_record_recv (c->tls_session, buffer, size);
+      nread = gnutls_record_recv (c->session->tls_session, buffer, size);
       if (nread < 0)
         {
           if (nread == GNUTLS_E_INTERRUPTED)
@@ -1591,16 +2444,23 @@ cookie_read (void *cookie, void *buffer, size_t size)
           if (nread == GNUTLS_E_AGAIN)
             {
               struct timeval tv;
-              
+
               tv.tv_sec = 0;
               tv.tv_usec = 50000;
-              select (0, NULL, NULL, NULL, &tv);
+              my_select (0, NULL, NULL, NULL, &tv);
               goto again;
             }
           if (nread == GNUTLS_E_REHANDSHAKE)
             goto again; /* A client is allowed to just ignore this request. */
+          if (nread == GNUTLS_E_PREMATURE_TERMINATION)
+            {
+              /* The server terminated the connection.  Close the TLS
+                 session, and indicate EOF using a short read.  */
+              close_tls_session (c->session);
+              return 0;
+            }
           log_info ("TLS network read failed: %s\n", gnutls_strerror (nread));
-          errno = EIO;
+          gpg_err_set_errno (EIO);
           return -1;
         }
     }
@@ -1609,7 +2469,25 @@ cookie_read (void *cookie, void *buffer, size_t size)
     {
       do
         {
-          nread = pth_read (c->fd, buffer, size);
+#ifdef HAVE_W32_SYSTEM
+          /* Under Windows we need to use recv for a socket.  */
+# if defined(USE_NPTH)
+          npth_unprotect ();
+# endif
+          nread = recv (c->sock->fd, buffer, size, 0);
+# if defined(USE_NPTH)
+          npth_protect ();
+# endif
+
+#else /*!HAVE_W32_SYSTEM*/
+
+# ifdef USE_NPTH
+          nread = npth_read (c->sock->fd, buffer, size);
+# else
+          nread = read (c->sock->fd, buffer, size);
+# endif
+
+#endif /*!HAVE_W32_SYSTEM*/
         }
       while (nread == -1 && errno == EINTR);
     }
@@ -1619,26 +2497,28 @@ cookie_read (void *cookie, void *buffer, size_t size)
       if (nread < c->content_length)
         c->content_length -= nread;
       else
-        c->content_length = 0;          
+        c->content_length = 0;
     }
 
-  return nread;
+  return (gpgrt_ssize_t)nread;
 }
 
 /* Write handler for estream.  */
-static ssize_t
-cookie_write (void *cookie, const void *buffer, size_t size)
+static gpgrt_ssize_t
+cookie_write (void *cookie, const void *buffer_arg, size_t size)
 {
+  const char *buffer = buffer_arg;
   cookie_t c = cookie;
   int nwritten = 0;
 
 #ifdef HTTP_USE_GNUTLS
-  if (c->tls_session)
+  if (c->use_tls && c->session && c->session->tls_session)
     {
       int nleft = size;
       while (nleft > 0)
         {
-          nwritten = gnutls_record_send (c->tls_session, buffer, nleft); 
+          nwritten = gnutls_record_send (c->session->tls_session,
+                                         buffer, nleft);
           if (nwritten <= 0)
             {
               if (nwritten == GNUTLS_E_INTERRUPTED)
@@ -1646,15 +2526,15 @@ cookie_write (void *cookie, const void *buffer, size_t size)
               if (nwritten == GNUTLS_E_AGAIN)
                 {
                   struct timeval tv;
-                  
+
                   tv.tv_sec = 0;
                   tv.tv_usec = 50000;
-                  select (0, NULL, NULL, NULL, &tv);
+                  my_select (0, NULL, NULL, NULL, &tv);
                   continue;
                 }
               log_info ("TLS network write failed: %s\n",
                         gnutls_strerror (nwritten));
-              errno = EIO;
+              gpg_err_set_errno (EIO);
               return -1;
             }
           nleft -= nwritten;
@@ -1664,15 +2544,42 @@ cookie_write (void *cookie, const void *buffer, size_t size)
   else
 #endif /*HTTP_USE_GNUTLS*/
     {
-      do
+      if ( write_server (c->sock->fd, buffer, size) )
         {
-          nwritten = pth_write (c->fd, buffer, size);
+          gpg_err_set_errno (EIO);
+          nwritten = -1;
         }
-      while (nwritten == -1 && errno == EINTR);
+      else
+        nwritten = size;
+    }
+
+  return (gpgrt_ssize_t)nwritten;
+}
+
+
+#ifdef HTTP_USE_GNUTLS
+/* Wrapper for gnutls_bye used by my_socket_unref.  */
+static void
+send_gnutls_bye (void *opaque)
+{
+  tls_session_t tls_session = opaque;
+  int ret;
+
+ again:
+  do
+    ret = gnutls_bye (tls_session, GNUTLS_SHUT_RDWR);
+  while (ret == GNUTLS_E_INTERRUPTED);
+  if (ret == GNUTLS_E_AGAIN)
+    {
+      struct timeval tv;
+
+      tv.tv_sec = 0;
+      tv.tv_usec = 50000;
+      my_select (0, NULL, NULL, NULL, &tv);
+      goto again;
     }
-  
-  return nwritten;
 }
+#endif /*HTTP_USE_GNUTLS*/
 
 /* Close handler for estream.  */
 static int
@@ -1684,15 +2591,15 @@ cookie_close (void *cookie)
     return 0;
 
 #ifdef HTTP_USE_GNUTLS
-  if (c->tls_session && !c->keep_socket)
-    {
-      /* This indicates that the read end has been closed.  */
-      gnutls_bye (c->tls_session, GNUTLS_SHUT_RDWR);
-    }
+  if (c->use_tls && c->session && c->session->tls_session)
+    my_socket_unref (c->sock, send_gnutls_bye, c->session->tls_session);
+  else
 #endif /*HTTP_USE_GNUTLS*/
-  if (c->fd != -1 && !c->keep_socket)
-    sock_close (c->fd);
+    if (c->sock)
+      my_socket_unref (c->sock, NULL, NULL);
 
+  if (c->session)
+    http_session_unref (c->session);
   xfree (c);
   return 0;
 }
@@ -1700,162 +2607,146 @@ cookie_close (void *cookie)
 
 
 \f
-/**** Test code ****/
-#ifdef TEST
-
-static gpg_error_t
-verify_callback (http_t hd, void *tls_context, int reserved)
-{
-  log_info ("verification of certificates skipped\n");
-  return 0;
-}
-
-
-
-/* static void */
-/* my_gnutls_log (int level, const char *text) */
-/* { */
-/*   fprintf (stderr, "gnutls:L%d: %s", level, text); */
-/* } */
-
-int
-main (int argc, char **argv)
+/* Verify the credentials of the server.  Returns 0 on success and
+   store the result in the session object.  */
+gpg_error_t
+http_verify_server_credentials (http_session_t sess)
 {
+#if HTTP_USE_NTBTLS
+  (void)sess;
+  return 0;  /* FIXME!! */
+#elif HTTP_USE_GNUTLS
+  static const char const errprefix[] = "TLS verification of peer failed";
   int rc;
-  parsed_uri_t uri;
-  uri_tuple_t r;
-  http_t hd;
-  int c;
-  gnutls_session_t tls_session = NULL;
-#ifdef HTTP_USE_GNUTLS
-  gnutls_certificate_credentials certcred;
-  const int certprio[] = { GNUTLS_CRT_X509, 0 };
-#endif /*HTTP_USE_GNUTLS*/
-  header_t hdr;
+  unsigned int status;
+  const char *hostname;
+  const gnutls_datum_t *certlist;
+  unsigned int certlistlen;
+  gnutls_x509_crt_t cert;
+  gpg_error_t err = 0;
 
-  es_init ();
-  log_set_prefix ("http-test", 1 | 4);
-  if (argc == 1)
+  sess->verify.done = 1;
+  sess->verify.status = 0;
+  sess->verify.rc = GNUTLS_E_CERTIFICATE_ERROR;
+
+  if (gnutls_certificate_type_get (sess->tls_session) != GNUTLS_CRT_X509)
     {
-      /*start_server (); */
-      return 0;
+      log_error ("%s: %s\n", errprefix, "not an X.509 certificate");
+      sess->verify.rc = GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE;
+      return gpg_error (GPG_ERR_GENERAL);
     }
 
-  if (argc != 2)
+  rc = gnutls_certificate_verify_peers2 (sess->tls_session, &status);
+  if (rc)
     {
-      fprintf (stderr, "usage: http-test uri\n");
-      return 1;
+      log_error ("%s: %s\n", errprefix, gnutls_strerror (rc));
+      if (!err)
+        err = gpg_error (GPG_ERR_GENERAL);
     }
-  argc--;
-  argv++;
+  else if (status)
+    {
+      log_error ("%s: status=0x%04x\n", errprefix, status);
+#if GNUTLS_VERSION_NUMBER >= 0x030104
+      {
+        gnutls_datum_t statusdat;
 
-#ifdef HTTP_USE_GNUTLS
-  rc = gnutls_global_init ();
-  if (rc)
-    log_error ("gnutls_global_init failed: %s\n", gnutls_strerror (rc));
-  rc = gnutls_certificate_allocate_credentials (&certcred);
-  if (rc)
-    log_error ("gnutls_certificate_allocate_credentials failed: %s\n",
-               gnutls_strerror (rc));
-/*   rc = gnutls_certificate_set_x509_trust_file */
-/*     (certcred, "ca.pem", GNUTLS_X509_FMT_PEM); */
-/*   if (rc) */
-/*     log_error ("gnutls_certificate_set_x509_trust_file failed: %s\n", */
-/*                gnutls_strerror (rc)); */
-  rc = gnutls_init (&tls_session, GNUTLS_CLIENT);
-  if (rc)
-    log_error ("gnutls_init failed: %s\n", gnutls_strerror (rc));
-  rc = gnutls_set_default_priority (tls_session);
-  if (rc)
-    log_error ("gnutls_set_default_priority failed: %s\n",
-               gnutls_strerror (rc));
-  rc = gnutls_certificate_type_set_priority (tls_session, certprio);
-  if (rc)
-    log_error ("gnutls_certificate_type_set_priority failed: %s\n",
-               gnutls_strerror (rc));
-  rc = gnutls_credentials_set (tls_session, GNUTLS_CRD_CERTIFICATE, certcred);
-  if (rc)
-    log_error ("gnutls_credentials_set failed: %s\n", gnutls_strerror (rc));
-/*   gnutls_global_set_log_function (my_gnutls_log); */
-/*   gnutls_global_set_log_level (4); */
+        if (!gnutls_certificate_verification_status_print
+            (status, GNUTLS_CRT_X509, &statusdat, 0))
+          {
+            log_info ("%s: %s\n", errprefix, statusdat.data);
+            gnutls_free (statusdat.data);
+          }
+      }
+#endif /*gnutls >= 3.1.4*/
 
-  http_register_tls_callback (verify_callback);
-#endif /*HTTP_USE_GNUTLS*/
+      sess->verify.status = status;
+      if (!err)
+        err = gpg_error (GPG_ERR_GENERAL);
+    }
 
-  rc = http_parse_uri (&uri, *argv);
-  if (rc)
+  hostname = sess->servername;
+  if (!hostname || !strchr (hostname, '.'))
     {
-      log_error ("`%s': %s\n", *argv, gpg_strerror (rc));
-      http_release_parsed_uri (uri);
-      return 1;
+      log_error ("%s: %s\n", errprefix, "hostname missing");
+      if (!err)
+        err = gpg_error (GPG_ERR_GENERAL);
     }
 
-  printf ("Scheme: %s\n", uri->scheme);
-  printf ("Host  : %s\n", uri->host);
-  printf ("Port  : %u\n", uri->port);
-  printf ("Path  : %s\n", uri->path);
-  for (r = uri->params; r; r = r->next)
+  certlist = gnutls_certificate_get_peers (sess->tls_session, &certlistlen);
+  if (!certlistlen)
     {
-      printf ("Params: %s", r->name);
-      if (!r->no_value)
-       {
-         printf ("=%s", r->value);
-         if (strlen (r->value) != r->valuelen)
-           printf (" [real length=%d]", (int) r->valuelen);
-       }
-      putchar ('\n');
+      log_error ("%s: %s\n", errprefix, "server did not send a certificate");
+      if (!err)
+        err = gpg_error (GPG_ERR_GENERAL);
+
+      /* Need to stop here.  */
+      if (err)
+        return err;
     }
-  for (r = uri->query; r; r = r->next)
+
+  rc = gnutls_x509_crt_init (&cert);
+  if (rc < 0)
     {
-      printf ("Query : %s", r->name);
-      if (!r->no_value)
-       {
-         printf ("=%s", r->value);
-         if (strlen (r->value) != r->valuelen)
-           printf (" [real length=%d]", (int) r->valuelen);
-       }
-      putchar ('\n');
+      if (!err)
+        err = gpg_error (GPG_ERR_GENERAL);
+      if (err)
+        return err;
     }
-  http_release_parsed_uri (uri);
-  uri = NULL;
 
-  rc = http_open_document (&hd, *argv, NULL, 
-                           HTTP_FLAG_NEED_HEADER,
-                           NULL, tls_session);
-  if (rc)
+  rc = gnutls_x509_crt_import (cert, &certlist[0], GNUTLS_X509_FMT_DER);
+  if (rc < 0)
     {
-      log_error ("can't get `%s': %s\n", *argv, gpg_strerror (rc));
-      return 1;
+      log_error ("%s: %s: %s\n", errprefix, "error importing certificate",
+                 gnutls_strerror (rc));
+      if (!err)
+        err = gpg_error (GPG_ERR_GENERAL);
     }
-  log_info ("open_http_document succeeded; status=%u\n",
-            http_get_status_code (hd));
-  for (hdr = hd->headers; hdr; hdr = hdr->next)
-    printf ("HDR: %s: %s\n", hdr->name, hdr->value);
-  switch (http_get_status_code (hd))
+
+  if (!gnutls_x509_crt_check_hostname (cert, hostname))
     {
-    case 200:
-      while ((c = es_getc (http_get_read_ptr (hd))) != EOF)
-        putchar (c);
-      break;
-    case 301:
-    case 302:
-      printf ("Redirected to `%s'\n", http_get_header (hd, "Location"));
-      break;
+      log_error ("%s: %s\n", errprefix, "hostname does not match");
+      if (!err)
+        err = gpg_error (GPG_ERR_GENERAL);
     }
-  http_close (hd, 0);
 
-#ifdef HTTP_USE_GNUTLS
-  gnutls_deinit (tls_session);
-  gnutls_certificate_free_credentials (certcred);
-  gnutls_global_deinit ();
-#endif /*HTTP_USE_GNUTLS*/
+  gnutls_x509_crt_deinit (cert);
 
-  return 0;
+  if (!err)
+    sess->verify.rc = 0;
+
+  if (sess->cert_log_cb)
+    {
+      const void *bufarr[10];
+      size_t buflenarr[10];
+      size_t n;
+
+      for (n = 0; n < certlistlen && n < DIM (bufarr)-1; n++)
+        {
+          bufarr[n] = certlist[n].data;
+          buflenarr[n] = certlist[n].size;
+        }
+      bufarr[n] = NULL;
+      buflenarr[n] = 0;
+      sess->cert_log_cb (sess, err, hostname, bufarr, buflenarr);
+    }
+
+  return err;
+#else /*!HTTP_USE_GNUTLS*/
+  (void)sess;
+  return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+#endif
 }
-#endif /*TEST*/
 
-/*
-Local Variables:
-compile-command: "gcc -I.. -I../gl -DTEST -DHAVE_CONFIG_H -Wall -O2 -g -o http-test http.c -L. -lcommon -L../jnlib -ljnlib -lgcrypt -lpth -lgnutls"
-End:
-*/
+/* Return the first query variable with the specified key.  If there
+   is no such variable, return NULL.  */
+struct uri_tuple_s *
+uri_query_lookup (parsed_uri_t uri, const char *key)
+{
+  struct uri_tuple_s *t;
+
+  for (t = uri->query; t; t = t->next)
+    if (strcmp (t->name, key) == 0)
+      return t;
+
+  return NULL;
+}