Merge T3490-proposal1 into master
[gnupg.git] / dirmngr / http.c
index b65a92a..8e778df 100644 (file)
@@ -1,27 +1,37 @@
 /* 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-2017 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 <https://www.gnu.org/licenses/>.
  */
 
 /* Simple HTTP client implementation.  We try to keep the code as
-   self-contained as possible.  There are some contraints however:
+   self-contained as possible.  There are some constraints however:
 
   - estream is required.  We now require estream because it provides a
     very useful and portable asprintf implementation and the fopencookie
   - 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
     an exit handler to cleanup the socket layer.
 */
 
-#warning Duplicated code with common/http.c 
-
 #ifdef HAVE_CONFIG_H
 # include <config.h>
 #endif
 #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 <sys/socket.h>
 # include <sys/time.h>
 # include <time.h>
+# include <fcntl.h>
 # include <netinet/in.h>
 # include <arpa/inet.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 "../common/util.h"
+#include "../common/i18n.h"
+#include "../common/sysutils.h" /* (gnupg_fd_t) */
+#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*/
+#include "http-common.h"
+
 
+#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)
@@ -110,6 +122,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. */
@@ -118,36 +133,59 @@ struct srventry
                         "01234567890@"                 \
                         "!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
 
-/* A long counter type.  */
-#ifdef HAVE_STRTOULL
-typedef unsigned long long longcounter_t;
-#define counter_strtoul(a) strtoull ((a), NULL, 10)
+#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 unsigned long longcounter_t;
-#define counter_strtoul(a) strtoul ((a), NULL, 10)
+typedef void *tls_session_t;
+# undef USE_TLS
 #endif
 
-#ifndef HTTP_USE_GNUTLS
-typedef void * gnutls_session_t;
-#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, unsigned int timeout,
+                                 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 gpg_error_t connect_server (const char *server, unsigned short port,
+                                   unsigned int flags, const char *srvtag,
+                                   unsigned int timeout, assuan_fd_t *r_sock);
+static gpgrt_ssize_t read_server (assuan_fd_t sock, void *buffer, size_t size);
+static gpg_error_t write_server (assuan_fd_t 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);
+#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS)
+static gpgrt_ssize_t simple_cookie_read (void *cookie,
+                                         void *buffer, size_t size);
+static gpgrt_ssize_t simple_cookie_write (void *cookie,
+                                          const void *buffer, size_t size);
+#endif
+
+/* 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,
@@ -156,25 +194,77 @@ 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;  
+  uint64_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;
 
 
+/* Simple cookie functions.  Here the cookie is an int with the
+ * socket. */
+#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS)
+static es_cookie_io_functions_t simple_cookie_functions =
+  {
+    simple_cookie_read,
+    simple_cookie_write,
+    NULL,
+    NULL
+  };
+#endif
+
+
+#if SIZEOF_UNSIGNED_LONG == 8
+# define HTTP_SESSION_MAGIC 0x0068545470534553 /* "hTTpSES" */
+#else
+# define HTTP_SESSION_MAGIC 0x68547365         /* "hTse"    */
+#endif
+
+/* The session object. */
+struct http_session_s
+{
+  unsigned long magic;
+
+  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 *);
+
+  /* The flags passed to the session object.  */
+  unsigned int flags;
+
+  /* A per-session TLS verification callback.  */
+  http_verify_cb_t verify_cb;
+  void *verify_cb_value;
+
+  /* The connect timeout */
+  unsigned int connect_timeout;
+};
 
 
 /* An object to save header lines. */
@@ -187,18 +277,26 @@ struct header_s
 typedef struct header_s *header_t;
 
 
+#if SIZEOF_UNSIGNED_LONG == 8
+# define HTTP_CONTEXT_MAGIC 0x0068545470435458 /* "hTTpCTX" */
+#else
+# define HTTP_CONTEXT_MAGIC 0x68546378         /* "hTcx"    */
+#endif
+
+
 /* Our handle context. */
-struct http_context_s 
+struct http_context_s
 {
+  unsigned long magic;
   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. */
@@ -208,6 +306,21 @@ struct http_context_s
 };
 
 
+/* Two flags to enable verbose and debug mode.  Although currently not
+ * set-able a value > 1 for OPT_DEBUG enables debugging of the session
+ * reference counting.  */
+static int opt_verbose;
+static int opt_debug;
+
+/* 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;
+
+/* The global callback for net activity.  */
+static void (*netactivity_cb)(void);
+
 
 \f
 #if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
@@ -236,14 +349,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),
@@ -257,6 +370,133 @@ 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;
+  if (opt_debug)
+    log_debug ("http.c:%d:socket_new: object %p for fd %d created\n",
+               lnr, so, (int)so->fd);
+  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++;
+  if (opt_debug > 1)
+    log_debug ("http.c:%d:socket_ref: object %p for fd %d refcount now %d\n",
+               lnr, so, (int)so->fd, so->refcount);
+  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--;
+      if (opt_debug > 1)
+        log_debug ("http.c:%d:socket_unref: object %p for fd %d ref now %d\n",
+                   lnr, so, (int)so->fd, so->refcount);
+
+      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*/
+
+
+#ifdef HTTP_USE_NTBTLS
+/* Connect the ntbls callback to our generic callback.  */
+static gpg_error_t
+my_ntbtls_verify_cb (void *opaque, ntbtls_t tls, unsigned int verify_flags)
+{
+  http_t hd = opaque;
+
+  (void)verify_flags;
+
+  log_assert (hd && hd->session && hd->session->verify_cb);
+  log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
+  log_assert (hd->session->magic == HTTP_SESSION_MAGIC);
+
+  return hd->session->verify_cb (hd->session->verify_cb_value,
+                                 hd, hd->session,
+                                 (hd->flags | hd->session->flags),
+                                 tls);
+}
+#endif /*HTTP_USE_NTBTLS*/
+
+
+
+\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;
+
+  log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
+  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
@@ -266,13 +506,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);
@@ -285,8 +525,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];
@@ -300,6 +541,7 @@ make_header_line (const char *prefix, const char *suffix,
       *p++ = '=';
       *p++ = '=';
     }
+  *p = 0;
   strcpy (p, suffix);
   return buffer;
 }
@@ -307,61 +549,494 @@ make_header_line (const char *prefix, const char *suffix,
 
 
 \f
+/* Set verbosity and debug mode for this module. */
 void
-http_register_tls_callback ( gpg_error_t (*cb) (http_t, void *, int) )
+http_set_verbose (int verbose, int debug)
 {
-#ifdef HTTP_USE_GNUTLS
-  tls_callback = (gpg_error_t (*) (http_t, gnutls_session_t, int))cb;
-#else
-  (void)cb;
-#endif  
+  opt_verbose = verbose;
+  opt_debug = debug;
+}
+
+
+/* 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, http_session_t, int))
+{
+  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
+    {
+      /* Warn if we can't access right now, but register it anyway in
+         case it becomes accessible later */
+      if (access (fname, F_OK))
+        log_info (_("can't access '%s': %s\n"), fname,
+                  gpg_strerror (gpg_error_from_syserror()));
+      sl = add_to_strlist (&tls_ca_certlist, fname);
+      if (*sl->d && !strcmp (sl->d + strlen (sl->d) - 4, ".pem"))
+        sl->flags = 1;
+    }
+}
+
+
+/* Register a callback which is called every time the HTTP mode has
+ * made a successful connection to some server.  */
+void
+http_register_netactivity_cb (void (*cb)(void))
+{
+  netactivity_cb = cb;
+}
+
+
+/* Call the netactivity callback if any.  */
+static void
+notify_netactivity (void)
+{
+  if (netactivity_cb)
+    netactivity_cb ();
+}
+
+
+
+#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)
+    {
+# if HTTP_USE_NTBTLS
+      /* FIXME!!
+         Possibly, ntbtls_get_transport and close those streams.
+         Somehow get SOCK to call my_socket_unref.
+      */
+      ntbtls_release (sess->tls_session);
+# elif 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;
+
+  log_assert (sess->magic == HTTP_SESSION_MAGIC);
+
+  sess->refcount--;
+  if (opt_debug > 1)
+    log_debug ("http.c:%d:session_unref: sess %p ref now %d\n",
+               lnr, sess, sess->refcount);
+  if (sess->refcount)
+    return;
+
+#ifdef USE_TLS
+  close_tls_session (sess);
+#endif /*USE_TLS*/
+
+  sess->magic = 0xdeadbeef;
+  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.
+ * Valid values for FLAGS are:
+ *   HTTP_FLAG_TRUST_DEF - Use the CAs set with http_register_tls_ca
+ *   HTTP_FLAG_TRUST_SYS - Also use the CAs defined by the system
+ *   HTTP_FLAG_NO_CRL    - Do not consult CRLs for https.
+ */
+gpg_error_t
+http_session_new (http_session_t *r_session,
+                  const char *intended_hostname, unsigned int flags,
+                  http_verify_cb_t verify_cb, void *verify_cb_value)
+{
+  gpg_error_t err;
+  http_session_t sess;
+
+  *r_session = NULL;
+
+  sess = xtrycalloc (1, sizeof *sess);
+  if (!sess)
+    return gpg_error_from_syserror ();
+  sess->magic = HTTP_SESSION_MAGIC;
+  sess->refcount = 1;
+  sess->flags = flags;
+  sess->verify_cb = verify_cb;
+  sess->verify_cb_value = verify_cb_value;
+  sess->connect_timeout = 0;
+
+#if HTTP_USE_NTBTLS
+  {
+    (void)intended_hostname; /* Not needed because we do not preload
+                              * certificates.  */
+
+    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;
+    int add_system_cas = !!(flags & HTTP_FLAG_TRUST_SYS);
+    int is_hkps_pool;
+
+    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;
+      }
+
+    is_hkps_pool = (intended_hostname
+                    && !ascii_strcasecmp (intended_hostname,
+                                          get_default_keyserver (1)));
+
+    /* If the user has not specified a CA list, and they are looking
+     * for the hkps pool from sks-keyservers.net, then default to
+     * Kristian's certificate authority:  */
+    if (!tls_ca_certlist && is_hkps_pool)
+      {
+        char *pemname = make_filename_try (gnupg_datadir (),
+                                           "sks-keyservers.netCA.pem", NULL);
+        if (!pemname)
+          {
+            err = gpg_error_from_syserror ();
+            log_error ("setting CA from file '%s' failed: %s\n",
+                       pemname, gpg_strerror (err));
+          }
+        else
+          {
+            rc = gnutls_certificate_set_x509_trust_file
+              (sess->certcred, pemname, GNUTLS_X509_FMT_PEM);
+            if (rc < 0)
+              log_info ("setting CA from file '%s' failed: %s\n",
+                        pemname, gnutls_strerror (rc));
+            xfree (pemname);
+          }
+      }
+
+    /* Add configured certificates to the session.  */
+    if ((flags & HTTP_FLAG_TRUST_DEF))
+      {
+        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));
+          }
+        if (!tls_ca_certlist && !is_hkps_pool)
+          add_system_cas = 1;
+      }
+
+    /* Add system certificates to the session.  */
+    if (add_system_cas)
+      {
+#if GNUTLS_VERSION_NUMBER >= 0x030014
+        static int shown;
+
+        rc = gnutls_certificate_set_x509_system_trust (sess->certcred);
+        if (rc < 0)
+          log_info ("setting system CAs failed: %s\n", gnutls_strerror (rc));
+        else if (!shown)
+          {
+            shown = 1;
+            log_info ("number of system provided CAs: %d\n", rc);
+          }
+#endif /* gnutls >= 3.0.20 */
+      }
+
+    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,
+                                     "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 && !HTTP_USE_NTBTLS*/
+  {
+    (void)intended_hostname;
+    (void)flags;
+  }
+#endif /*!HTTP_USE_GNUTLS && !HTTP_USE_NTBTLS*/
+
+  if (opt_debug > 1)
+    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++;
+      if (opt_debug > 1)
+        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;
+}
+
+
+/* Set the TIMEOUT in milliseconds for the connection's connect
+ * calls.  Using 0 disables the timeout.  */
+void
+http_session_set_timeout (http_session_t sess, unsigned int timeout)
+{
+  sess->connect_timeout = timeout;
 }
 
 
 
-/* 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 for 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->magic = HTTP_CONTEXT_MAGIC;
   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,
+                        hd->session? hd->session->connect_timeout : 0,
+                        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.  TIMEOUT is in milliseconds. */
+gpg_error_t
+http_raw_connect (http_t *r_hd, const char *server, unsigned short port,
+                  unsigned int flags, const char *srvtag, unsigned int timeout)
+{
+  gpg_error_t err = 0;
+  http_t hd;
+  cookie_t cookie;
+
+  *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);
+        }
+      /* Non-blocking connects do not work with our Tor proxy because
+       * we can't continue the Socks protocol after the EINPROGRESS.
+       * Disable the timeout to use a blocking connect.  */
+      timeout = 0;
+    }
+
+  /* Create the handle. */
+  hd = xtrycalloc (1, sizeof *hd);
+  if (!hd)
+    return gpg_error_from_syserror ();
+  hd->magic = HTTP_CONTEXT_MAGIC;
+  hd->req_type = HTTP_REQ_OPAQUE;
+  hd->flags = flags;
+
+  /* Connect.  */
+  {
+    assuan_fd_t sock;
+
+    err = connect_server (server, port, hd->flags, srvtag, timeout, &sock);
+    if (err)
+      {
+        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
@@ -370,11 +1045,15 @@ http_open (http_t *r_hd, http_req_t reqtype, const char *url,
 }
 
 
+
+
 void
 http_start_data (http_t hd)
 {
   if (!hd->in_data)
     {
+      if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
+        log_debug_with_string ("\r\n", "http.c:request-header:");
       es_fputs ("\r\n", hd->fp_write);
       es_fflush (hd->fp_write);
       hd->in_data = 1;
@@ -389,45 +1068,55 @@ http_wait_response (http_t hd)
 {
   gpg_error_t err;
   cookie_t cookie;
+  int use_tls;
 
   /* 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;
+  use_tls = cookie->use_tls;
   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.  */
+     keyserver didn't worked without it.  */
   if ((hd->flags & HTTP_FLAG_SHUTDOWN))
-    shutdown (hd->sock, 1);
+    shutdown (FD2INT (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 = 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;
 }
 
@@ -437,14 +1126,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;
 
@@ -461,12 +1151,23 @@ 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);
+
+  log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
+
+  /* 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);
+  hd->magic = 0xdeadbeef;
   http_release_parsed_uri (hd->uri);
   while (hd->headers)
     {
@@ -498,21 +1199,63 @@ 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 in 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 + 2 * strlen (uri) + 1);
+  if (!*ret_uri)
+    return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+  strcpy ((*ret_uri)->buffer, uri);
+  strcpy ((*ret_uri)->buffer + strlen (uri) + 1, uri);
+  (*ret_uri)->original = (*ret_uri)->buffer + strlen (uri) + 1;
+  ec = do_parse_uri (*ret_uri, 0, no_scheme_check, force_tls);
+  if (ec)
+    {
+      http_release_parsed_uri (*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)
 {
@@ -520,6 +1263,11 @@ http_release_parsed_uri (parsed_uri_t uri)
     {
       uri_tuple_t r, r2;
 
+      for (r = uri->params; r; r = r2)
+       {
+         r2 = r->next;
+         xfree (r);
+       }
       for (r = uri->query; r; r = r2)
        {
          r2 = r->next;
@@ -530,8 +1278,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;
@@ -545,42 +1294,53 @@ 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;
+  uri->explicit_port = 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;
 
@@ -594,60 +1354,86 @@ 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->explicit_port = 1;
            }
 
-         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 includes 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;
 }
 
@@ -701,16 +1487,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;
@@ -720,7 +1531,7 @@ insert_escapes (char *buffer, const char *string,
        {
          if (buffer)
            {
-             sprintf (buffer, "%%%02X", *s);
+             snprintf (buffer, 4, "%%%02X", *s);
              buffer += 3;
            }
          n += 3;
@@ -730,11 +1541,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)
 {
@@ -751,6 +1571,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
@@ -791,14 +1632,42 @@ 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, unsigned int timeout,
+              strlist_t headers)
 {
-  gnutls_session_t tls_session;
   gpg_error_t err;
   const char *server;
   char *request, *p;
@@ -806,23 +1675,80 @@ 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;
+  assuan_fd_t sock;
+#ifdef USE_TLS
+  int have_http_proxy = 0;
+#endif
 
+  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*/
 
-  tls_session = hd->tls_context;
-  if (hd->uri->use_tls && !tls_session)
+  if ((hd->flags & HTTP_FLAG_FORCE_TOR))
     {
-      log_error ("TLS requested but no GNUTLS context provided\n");
-      return gpg_error (GPG_ERR_INTERNAL);
+      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);
+        }
+      /* Non-blocking connects do not work with our Tor proxy because
+       * we can't continue the Socks protocol after the EINPROGRESS.
+       * Disable the timeout to use a blocking connect.  */
+      timeout = 0;
     }
 
   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;
+        }
+
+# 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*/
+    }
+#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;
@@ -830,14 +1756,34 @@ send_request (http_t hd, const char *auth, const char *proxy)
       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)
+        ;
+#ifdef USE_TLS
+      else if (!strcmp (uri->scheme, "http"))
+        have_http_proxy = 1;
+#endif
+      else if (!strcmp (uri->scheme, "socks4")
+               || !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)
@@ -848,75 +1794,303 @@ 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);
-      save_errno = errno;
+      err = connect_server (*uri->host ? uri->host : "localhost",
+                            uri->port ? uri->port : 80,
+                            hd->flags, NULL, timeout, &sock);
       http_release_parsed_uri (uri);
     }
   else
     {
-      hd->sock = connect_server (server, port, hd->flags, hd->uri->scheme);
-      save_errno = errno;
+      err = connect_server (server, port, hd->flags, srvtag, timeout, &sock);
     }
 
-  if (hd->sock == -1)
+  if (err)
+    {
+      xfree (proxy_authstr);
+      return err;
+    }
+  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 USE_TLS
+  if (have_http_proxy && hd->uri->use_tls)
+    {
+      int saved_flags;
+      cookie_t cookie;
+
+      /* Try to use the CONNECT method to proxy our TLS stream.  */
+      request = es_bsprintf
+        ("CONNECT %s:%hu HTTP/1.0\r\nHost: %s:%hu\r\n%s",
+         httphost ? httphost : server,
+         port,
+         httphost ? httphost : server,
+         port,
+         proxy_authstr ? proxy_authstr : "");
+      xfree (proxy_authstr);
+      proxy_authstr = NULL;
+
+      if (! request)
+        return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+
+      if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
+        log_debug_with_string (request, "http.c:request:");
+
+      cookie = xtrycalloc (1, sizeof *cookie);
+      if (! cookie)
+        {
+          err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+          xfree (request);
+          return err;
+        }
+      cookie->sock = my_socket_ref (hd->sock);
+      hd->write_cookie = cookie;
+
+      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);
+          xfree (request);
+          hd->write_cookie = NULL;
+          return err;
+        }
+      else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
+        err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+
+      xfree (request);
+      request = NULL;
+
+      /* Make sure http_wait_response doesn't close the stream.  */
+      saved_flags = hd->flags;
+      hd->flags &= ~HTTP_FLAG_SHUTDOWN;
+
+      /* Get the response.  */
+      err = http_wait_response (hd);
+
+      /* Restore flags, destroy stream.  */
+      hd->flags = saved_flags;
+      es_fclose (hd->fp_read);
+      hd->fp_read = NULL;
+      hd->read_cookie = NULL;
+
+      /* Reset state.  */
+      hd->in_data = 0;
+
+      if (err)
+        return err;
+
+      if (hd->status_code != 200)
+        {
+          request = es_bsprintf
+            ("CONNECT %s:%hu",
+             httphost ? httphost : server,
+             port);
+
+          log_error (_("error accessing '%s': http status %u\n"),
+                     request ? request : "out of core",
+                     http_get_status_code (hd));
+
+          xfree (request);
+          return gpg_error (GPG_ERR_NO_DATA);
+        }
+
+      /* We are done with the proxy, the code below will establish a
+       * TLS session and talk directly to the target server.  */
+      http_proxy = NULL;
+    }
+#endif /* USE_TLS */
+
+#if HTTP_USE_NTBTLS
   if (hd->uri->use_tls)
     {
-      int rc;
+      estream_t in, out;
 
-      gnutls_transport_set_ptr (tls_session, (gnutls_transport_ptr_t)hd->sock);
-      do
+      my_socket_ref (hd->sock);
+
+      /* Until we support send/recv in estream under Windows we need
+       * to use es_fopencookie.  */
+#ifdef HAVE_W32_SYSTEM
+      in = es_fopencookie ((void*)(unsigned int)hd->sock->fd, "rb",
+                           simple_cookie_functions);
+#else
+      in = es_fdopen_nc (hd->sock->fd, "rb");
+#endif
+      if (!in)
         {
-          rc = gnutls_handshake (tls_session);
+          err = gpg_error_from_syserror ();
+          xfree (proxy_authstr);
+          return err;
         }
-      while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN);
-      if (rc < 0)
+
+#ifdef HAVE_W32_SYSTEM
+      out = es_fopencookie ((void*)(unsigned int)hd->sock->fd, "wb",
+                            simple_cookie_functions);
+#else
+      out = es_fdopen_nc (hd->sock->fd, "wb");
+#endif
+      if (!out)
         {
-          log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc));
+          err = gpg_error_from_syserror ();
+          es_fclose (in);
           xfree (proxy_authstr);
-          return gpg_error (GPG_ERR_NETWORK);
+          return err;
         }
 
-      if (tls_callback)
+      err = ntbtls_set_transport (hd->session->tls_session, in, out);
+      if (err)
+        {
+          log_info ("TLS set_transport failed: %s <%s>\n",
+                    gpg_strerror (err), gpg_strsource (err));
+          xfree (proxy_authstr);
+          return err;
+        }
+
+#ifdef HTTP_USE_NTBTLS
+      if (hd->session->verify_cb)
         {
-          err = tls_callback (hd, tls_session, 0);
+          err = ntbtls_set_verify_cb (hd->session->tls_session,
+                                      my_ntbtls_verify_cb, hd);
           if (err)
             {
-              log_info ("TLS connection authentication failed: %s\n",
-                        gpg_strerror (err));
+              log_error ("ntbtls_set_verify_cb failed: %s\n",
+                         gpg_strerror (err));
+              xfree (proxy_authstr);
+              return err;
+            }
+        }
+#endif /*HTTP_USE_NTBTLS*/
+
+      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;
+
+      /* Try the available verify callbacks until one returns success
+       * or a real error.  Note that NTBTLS does the verification
+       * during the handshake via   */
+#ifdef HTTP_USE_NTBTLS
+      err = 0; /* Fixme check that the CB has been called.  */
+#else
+      err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+#endif
+
+      if (hd->session->verify_cb
+          && gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
+          && gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
+        err = hd->session->verify_cb (hd->session->verify_cb_value,
+                                      hd, hd->session,
+                                      (hd->flags | hd->session->flags),
+                                      hd->session->tls_session);
+
+      if (tls_callback
+          && gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
+          && gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
+        err = tls_callback (hd, hd->session, 0);
+
+      if (gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR
+          && gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
+        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;
+
+      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);
+
+    handshake_again:
+      do
+        {
+          rc = gnutls_handshake (hd->session->tls_session);
+        }
+      while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN);
+      if (rc < 0)
+        {
+          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 %s: %s (alert %d)\n",
+                        rc == GNUTLS_E_WARNING_ALERT_RECEIVED
+                        ? "warning" : "failed",
+                        alertstr, (int)alertno);
+              if (alertno == GNUTLS_A_UNRECOGNIZED_NAME && server)
+                log_info ("  (sent server name '%s')\n", server);
+
+              if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED)
+                goto handshake_again;
+            }
+          else
+            log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc));
+          xfree (proxy_authstr);
+          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)
+        {
+          log_info ("TLS connection authentication failed: %s\n",
+                    gpg_strerror (err));
+          xfree (proxy_authstr);
+          return err;
+        }
     }
 #endif /*HTTP_USE_GNUTLS*/
 
   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);
         }
@@ -926,7 +2100,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);
@@ -934,74 +2108,104 @@ 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)
+
+      if (port == (hd->uri->use_tls? 443 : 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;
     }
 
+  if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
+    log_debug_with_string (request, "http.c: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)
+  {
+    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)
     {
-      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)
-    {
-      xfree (cookie);
-      hd->write_cookie = NULL;
-      err = gpg_error_from_syserror ();
+      for (;headers; headers=headers->next)
+        {
+          if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
+            log_debug_with_string (headers->d, "http.c:request-header:");
+          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);
@@ -1091,7 +2295,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;
@@ -1106,17 +2310,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. */
+         recommended.  We use a straightforward implementation. */
       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;
@@ -1126,23 +2330,22 @@ 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;
   if (h)
     {
       /* We have already seen a line with that name.  Thus we assume
-         it is a comma separated list and merge them.  */
-      p = xtrymalloc (strlen (h->value) + 1 + strlen (value)+ 1);
+       * it is a comma separated list and merge them.  */
+      p = strconcat (h->value, ",", value, NULL);
       if (!p)
-        return gpg_error_from_syserror ();
-      strcpy (stpcpy (stpcpy (p, h->value), ","), value);
+        return gpg_err_code_from_syserror ();
       xfree (h->value);
       h->value = p;
       return 0;
@@ -1151,13 +2354,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;
@@ -1168,12 +2371,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)
 {
@@ -1186,12 +2387,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;
@@ -1215,14 +2439,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",
-                  (int)strlen(line)-(*line&&line[1]?2:0),line);
+       return GPG_ERR_EOF;
+
+      if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
+        log_debug_with_string (line, "http.c:response:\n");
     }
   while (!*line);
 
@@ -1259,21 +2483,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 (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
+        log_info ("http.c: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);
@@ -1285,7 +2509,7 @@ parse_response (http_t hd)
       if (s)
         {
           cookie->content_length_valid = 1;
-          cookie->content_length = counter_strtoul (s);
+          cookie->content_length = string_to_u64 (s);
         }
     }
 
@@ -1335,14 +2559,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 */
 
@@ -1369,70 +2593,285 @@ start_server ()
 }
 #endif
 
-/* Actually connect to a server.  Returns the file descripto or -1 on
-   error.  ERRNO is set on error. */
+
+
+/* Return true if SOCKS shall be used.  This is the case if tor_mode
+ * is enabled and the desired address is not the loopback address.
+ * This function is basically a copy of the same internal function in
+ * Libassuan.  */
 static int
+use_socks (struct sockaddr_storage *addr)
+{
+  int mode;
+
+  if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
+    return 0;  /* Not in Tor mode.  */
+  else if (addr->ss_family == AF_INET6)
+    {
+      struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr;
+      const unsigned char *s;
+      int i;
+
+      s = (unsigned char *)&addr_in6->sin6_addr.s6_addr;
+      if (s[15] != 1)
+        return 1;   /* Last octet is not 1 - not the loopback address.  */
+      for (i=0; i < 15; i++, s++)
+        if (*s)
+          return 1; /* Non-zero octet found - not the loopback address.  */
+
+      return 0; /* This is the loopback address.  */
+    }
+  else if (addr->ss_family == AF_INET)
+    {
+      struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
+
+      if (*(unsigned char*)&addr_in->sin_addr.s_addr == 127)
+        return 0; /* Loopback (127.0.0.0/8) */
+
+      return 1;
+    }
+  else
+    return 0;
+}
+
+
+/* Wrapper around assuan_sock_new which takes the domain from an
+ * address parameter.  */
+static assuan_fd_t
+my_sock_new_for_addr (struct sockaddr_storage *addr, int type, int proto)
+{
+  int domain;
+
+  if (use_socks (addr))
+    {
+      /* Libassaun always uses 127.0.0.1 to connect to the socks
+       * server (i.e. the Tor daemon).  */
+      domain = AF_INET;
+    }
+  else
+    domain = addr->ss_family;
+
+  return assuan_sock_new (domain, type, proto);
+}
+
+
+/* Call WSAGetLastError and map it to a libgpg-error.  */
+#ifdef HAVE_W32_SYSTEM
+static gpg_error_t
+my_wsagetlasterror (void)
+{
+  int wsaerr;
+  gpg_err_code_t ec;
+
+  wsaerr = WSAGetLastError ();
+  switch (wsaerr)
+    {
+    case WSAENOTSOCK:        ec = GPG_ERR_EINVAL;       break;
+    case WSAEWOULDBLOCK:     ec = GPG_ERR_EAGAIN;       break;
+    case ERROR_BROKEN_PIPE:  ec = GPG_ERR_EPIPE;        break;
+    case WSANOTINITIALISED:  ec = GPG_ERR_ENOSYS;       break;
+    case WSAENOBUFS:         ec = GPG_ERR_ENOBUFS;      break;
+    case WSAEMSGSIZE:        ec = GPG_ERR_EMSGSIZE;     break;
+    case WSAECONNREFUSED:    ec = GPG_ERR_ECONNREFUSED; break;
+    case WSAEISCONN:         ec = GPG_ERR_EISCONN;      break;
+    case WSAEALREADY:        ec = GPG_ERR_EALREADY;     break;
+    case WSAETIMEDOUT:       ec = GPG_ERR_ETIMEDOUT;    break;
+    default:                 ec = GPG_ERR_EIO;          break;
+    }
+
+  return gpg_err_make (default_errsource, ec);
+}
+#endif /*HAVE_W32_SYSTEM*/
+
+
+/* Connect SOCK and return GPG_ERR_ETIMEOUT if a connection could not
+ * be established within TIMEOUT milliseconds.  0 indicates the
+ * system's default timeout.  The other args are the usual connect
+ * args.  On success 0 is returned, on timeout GPG_ERR_ETIMEDOUT, and
+ * another error code for other errors.  On timeout the caller needs
+ * to close the socket as soon as possible to stop an ongoing
+ * handshake.
+ *
+ * This implementation is for well-behaving systems; see Stevens,
+ * Network Programming, 2nd edition, Vol 1, 15.4.  */
+static gpg_error_t
+connect_with_timeout (assuan_fd_t sock,
+                      struct sockaddr *addr, int addrlen,
+                      unsigned int timeout)
+{
+  gpg_error_t err;
+  int syserr;
+  socklen_t slen;
+  fd_set rset, wset;
+  struct timeval tval;
+  int n;
+
+#ifndef HAVE_W32_SYSTEM
+  int oflags;
+# define RESTORE_BLOCKING()  do {  \
+    fcntl (sock, F_SETFL, oflags); \
+  } while (0)
+#else /*HAVE_W32_SYSTEM*/
+# define RESTORE_BLOCKING()  do {                       \
+    unsigned long along = 0;                            \
+    ioctlsocket (FD2INT (sock), FIONBIO, &along);       \
+  } while (0)
+#endif /*HAVE_W32_SYSTEM*/
+
+
+  if (!timeout)
+    {
+      /* Shortcut.  */
+      if (assuan_sock_connect (sock, addr, addrlen))
+        err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+      else
+        err = 0;
+      return err;
+    }
+
+  /* Switch the socket into non-blocking mode.  */
+#ifdef HAVE_W32_SYSTEM
+  {
+    unsigned long along = 1;
+    if (ioctlsocket (FD2INT (sock), FIONBIO, &along))
+      return my_wsagetlasterror ();
+  }
+#else
+  oflags = fcntl (sock, F_GETFL, 0);
+  if (fcntl (sock, F_SETFL, oflags | O_NONBLOCK))
+    return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+#endif
+
+  /* Do the connect.  */
+  if (!assuan_sock_connect (sock, addr, addrlen))
+    {
+      /* Immediate connect.  Restore flags. */
+      RESTORE_BLOCKING ();
+      return 0; /* Success.  */
+    }
+  err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+  if (gpg_err_code (err) != GPG_ERR_EINPROGRESS
+#ifdef HAVE_W32_SYSTEM
+      && gpg_err_code (err) != GPG_ERR_EAGAIN
+#endif
+      )
+    {
+      RESTORE_BLOCKING ();
+      return err;
+    }
+
+  FD_ZERO (&rset);
+  FD_SET (FD2INT (sock), &rset);
+  wset = rset;
+  tval.tv_sec = timeout / 1000;
+  tval.tv_usec = (timeout % 1000) * 1000;
+
+  n = my_select (FD2INT(sock)+1, &rset, &wset, NULL, &tval);
+  if (n < 0)
+    {
+      err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+      RESTORE_BLOCKING ();
+      return err;
+    }
+  if (!n)
+    {
+      /* Timeout: We do not restore the socket flags on timeout
+       * because the caller is expected to close the socket.  */
+      return gpg_err_make (default_errsource, GPG_ERR_ETIMEDOUT);
+    }
+  if (!FD_ISSET (sock, &rset) && !FD_ISSET (sock, &wset))
+    {
+      /* select misbehaved.  */
+      return gpg_err_make (default_errsource, GPG_ERR_SYSTEM_BUG);
+    }
+
+  slen = sizeof (syserr);
+  if (getsockopt (FD2INT(sock), SOL_SOCKET, SO_ERROR,
+                  (void*)&syserr, &slen) < 0)
+    {
+      /* Assume that this is Solaris which returns the error in ERRNO.  */
+      err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+    }
+  else if (syserr)
+    err = gpg_err_make (default_errsource, gpg_err_code_from_errno (syserr));
+  else
+    err = 0; /* Connected.  */
+
+  RESTORE_BLOCKING ();
+
+  return err;
+
+#undef RESTORE_BLOCKING
+}
+
+
+/* Actually connect to a server.  On success 0 is returned and the
+ * file descriptor for the socket is stored at R_SOCK; on error an
+ * error code is returned and ASSUAN_INVALID_FD is stored at R_SOCK.
+ * TIMEOUT is the connect timeout in milliseconds.  Note that the
+ * function tries to connect to all known addresses and the timeout is
+ * for each one. */
+static gpg_error_t
 connect_server (const char *server, unsigned short port,
-                unsigned int flags, const char *srvtag)
+                unsigned int flags, const char *srvtag, unsigned int timeout,
+                assuan_fd_t *r_sock)
 {
-  int sock = -1;
-  int srvcount = 0;
+  gpg_error_t err;
+  assuan_fd_t sock = ASSUAN_INVALID_FD;
+  unsigned int srvcount = 0;
   int hostfound = 0;
+  int anyhostaddr = 0;
   int srv, connected;
-  int last_errno = 0;
+  gpg_error_t last_err = 0;
   struct srventry *serverlist = NULL;
 
-#ifdef HAVE_W32_SYSTEM
-  unsigned long inaddr;
+  *r_sock = ASSUAN_INVALID_FD;
 
-#ifndef HTTP_NO_WSASTARTUP
+#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;
+      if (opt_debug)
+        log_debug ("http.c:connect_server:onion: name='%s' port=%hu\n",
+                   server, port);
+      sock = assuan_sock_connect_byname (server, port, 0, NULL,
+                                         ASSUAN_SOCK_TOR);
+      if (sock == ASSUAN_INVALID_FD)
+        {
+          err = gpg_err_make (default_errsource,
+                              (errno == EHOSTUNREACH)? GPG_ERR_UNKNOWN_HOST
+                              : gpg_err_code_from_syserror ());
+          log_error ("can't connect to '%s': %s\n", server, gpg_strerror (err));
+          return err;
+        }
+
+      notify_netactivity ();
+      *r_sock = sock;
+      return 0;
+
+#else /*!ASSUAN_SOCK_TOR*/
+
+      err = gpg_err_make (default_errsource, GPG_ERR_ENETUNREACH);
+      return ASSUAN_INVALID_FD;
+
+#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)
-       {
-         char srvname[MAXDNAME];
-
-         stpcpy (stpcpy (stpcpy (stpcpy (srvname,"_"), srvtag),
-                           "._tcp."), server);
-         srvcount = getsrv (srvname, &serverlist);
-       }
+      err = get_dns_srv (server, srvtag, NULL, &serverlist, &srvcount);
+      if (err)
+        log_info ("getting '%s' SRV for '%s' failed: %s\n",
+                  srvtag, server, gpg_strerror (err));
+      /* Note that on error SRVCOUNT is zero.  */
+      err = 0;
     }
-#else /*!USE_DNS_SRV*/
-  (void)flags;
-  (void)srvtag;
-#endif /*!USE_DNS_SRV*/
 
   if (!serverlist)
     {
@@ -1440,134 +2879,192 @@ connect_server (const char *server, unsigned short port,
         up a fake SRV record. */
       serverlist = xtrycalloc (1, sizeof *serverlist);
       if (!serverlist)
-        return -1; /* Out of core.  */
+        return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+
       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. */
+      if (opt_debug)
+        log_debug ("http.c:connect_server: trying name='%s' port=%hu\n",
+                   serverlist[srv].target, port);
+      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));
+          last_err = 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 = my_sock_new_for_addr (ai->addr, ai->socktype, ai->protocol);
+          if (sock == ASSUAN_INVALID_FD)
             {
-              int save_errno = errno;
-              log_error ("error creating socket: %s\n", strerror (errno));
-              freeaddrinfo (res);
+              err = gpg_err_make (default_errsource,
+                                  gpg_err_code_from_syserror ());
+              log_error ("error creating socket: %s\n", gpg_strerror (err));
+              free_dns_addrinfo (aibuf);
               xfree (serverlist);
-              errno = save_errno;
-              return -1;
+              return err;
             }
-          
-          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)))
-            last_errno = errno;
+          anyhostaddr = 1;
+          err = connect_with_timeout (sock, (struct sockaddr *)ai->addr,
+                                      ai->addrlen, timeout);
+          if (err)
+            {
+              last_err = err;
+            }
           else
             {
               connected = 1;
-              break;
+              notify_netactivity ();
             }
         }
+      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, gpg_strerror (last_err));
 #endif
-      if (sock != -1)
-       sock_close (sock);
-      errno = last_errno;
-      return -1;
+        }
+      err = last_err? last_err : gpg_err_make (default_errsource,
+                                               GPG_ERR_UNKNOWN_HOST);
+      if (sock != ASSUAN_INVALID_FD)
+       assuan_sock_close (sock);
+      return err;
+    }
+
+  *r_sock = sock;
+  return 0;
+}
+
+
+/* Helper to read from a socket.  This handles npth things and
+ * EINTR.  */
+static gpgrt_ssize_t
+read_server (assuan_fd_t sock, void *buffer, size_t size)
+{
+  int nread;
+
+  do
+    {
+#ifdef HAVE_W32_SYSTEM
+      /* Under Windows we need to use recv for a socket.  */
+# if defined(USE_NPTH)
+      npth_unprotect ();
+# endif
+      nread = recv (FD2INT (sock), buffer, size, 0);
+# if defined(USE_NPTH)
+      npth_protect ();
+# endif
+
+#else /*!HAVE_W32_SYSTEM*/
+
+# ifdef USE_NPTH
+      nread = npth_read (sock, buffer, size);
+# else
+      nread = read (sock, buffer, size);
+# endif
+
+#endif /*!HAVE_W32_SYSTEM*/
     }
-  return sock;
+  while (nread == -1 && errno == EINTR);
+
+  return nread;
 }
 
 
+static gpg_error_t
+write_server (assuan_fd_t 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 (FD2INT (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;
@@ -1581,11 +3078,22 @@ cookie_read (void *cookie, void *buffer, size_t size)
         size = c->content_length;
     }
 
-#ifdef HTTP_USE_GNUTLS
-  if (c->tls_session)
+#if HTTP_USE_NTBTLS
+  if (c->use_tls && c->session && c->session->tls_session)
+    {
+      estream_t in, out;
+
+      ntbtls_get_stream (c->session->tls_session, &in, &out);
+      nread = es_fread (buffer, 1, size, in);
+      if (opt_debug)
+        log_debug ("TLS network read: %d/%zu\n", nread, size);
+    }
+  else
+#elif HTTP_USE_GNUTLS
+  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)
@@ -1593,27 +3101,30 @@ 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;
         }
     }
   else
 #endif /*HTTP_USE_GNUTLS*/
     {
-      do
-        {
-          nread = pth_read (c->fd, buffer, size);
-        }
-      while (nread == -1 && errno == EINTR);
+      nread = read_server (c->sock->fd, buffer, size);
     }
 
   if (c->content_length_valid && nread > 0)
@@ -1621,26 +3132,42 @@ 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 HTTP_USE_NTBTLS
+  if (c->use_tls && c->session && c->session->tls_session)
+    {
+      estream_t in, out;
+
+      ntbtls_get_stream (c->session->tls_session, &in, &out);
+      if (size == 0)
+        es_fflush (out);
+      else
+        nwritten = es_fwrite (buffer, 1, size, out);
+      if (opt_debug)
+        log_debug ("TLS network write: %d/%zu\n", nwritten, size);
+    }
+  else
+#elif HTTP_USE_GNUTLS
+  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)
@@ -1648,15 +3175,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;
@@ -1666,15 +3193,70 @@ 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;
+}
+
+
+#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS)
+static gpgrt_ssize_t
+simple_cookie_read (void *cookie, void *buffer, size_t size)
+{
+  assuan_fd_t sock = (assuan_fd_t)cookie;
+  return read_server (sock, buffer, size);
+}
+
+static gpgrt_ssize_t
+simple_cookie_write (void *cookie, const void *buffer_arg, size_t size)
+{
+  assuan_fd_t sock = (assuan_fd_t)cookie;
+  const char *buffer = buffer_arg;
+  int nwritten;
+
+  if (write_server (sock, buffer, size))
+    {
+      gpg_err_set_errno (EIO);
+      nwritten = -1;
     }
-  
-  return nwritten;
+  else
+    nwritten = size;
+
+  return (gpgrt_ssize_t)nwritten;
 }
+#endif /*HAVE_W32_SYSTEM*/
+
+
+#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;
+    }
+}
+#endif /*HTTP_USE_GNUTLS*/
 
 /* Close handler for estream.  */
 static int
@@ -1685,16 +3267,24 @@ cookie_close (void *cookie)
   if (!c)
     return 0;
 
-#ifdef HTTP_USE_GNUTLS
-  if (c->tls_session && !c->keep_socket)
+#if HTTP_USE_NTBTLS
+  if (c->use_tls && c->session && c->session->tls_session)
     {
-      /* This indicates that the read end has been closed.  */
-      gnutls_bye (c->tls_session, GNUTLS_SHUT_RDWR);
+      /* FIXME!! Possibly call ntbtls_close_notify for close
+         of write stream.  */
+      my_socket_unref (c->sock, NULL, NULL);
     }
+  else
+#elif HTTP_USE_GNUTLS
+  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;
 }
@@ -1702,162 +3292,143 @@ 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_GNUTLS
+  static const char 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;
+
+  sess->verify.done = 1;
+  sess->verify.status = 0;
+  sess->verify.rc = GNUTLS_E_CERTIFICATE_ERROR;
 
-  es_init ();
-  log_set_prefix ("http-test", 1 | 4);
-  if (argc == 1)
+  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;
+}