common: Minor change of hex2str to allow for embedded nul.
[gnupg.git] / common / http.c
index 3749832..c2cac16 100644 (file)
@@ -1,6 +1,8 @@
 /* http.c  -  HTTP protocol handler
  * Copyright (C) 1999, 2001, 2002, 2003, 2004, 2006, 2009, 2010,
  *               2011 Free Software Foundation, Inc.
+ * Copyright (C) 2014 Werner Koch
+ * Copyright (C) 2015 g10 Code GmbH
  *
  * This file is part of GnuPG.
  *
@@ -38,8 +40,9 @@
   - fixme: list other requirements.
 
 
-  - With HTTP_USE_GNUTLS support for https is provided (this also
-    requires estream).
+  - With HTTP_USE_NTBTLS or HTTP_USE_GNUTLS support for https is
+    provided (this also requires estream).
+
   - With HTTP_NO_WSASTARTUP the socket initialization is not done
     under Windows.  This is useful if the socket layer has already
     been initialized elsewhere.  This also avoids the installation of
@@ -58,6 +61,9 @@
 #include <unistd.h>
 
 #ifdef HAVE_W32_SYSTEM
+# ifdef HAVE_WINSOCK2_H
+#  include <winsock2.h>
+# endif
 # include <windows.h>
 #else /*!HAVE_W32_SYSTEM*/
 # include <sys/types.h>
 #endif /*!HAVE_W32_SYSTEM*/
 
 #ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth.  */
-# undef HAVE_NPTH
 # undef USE_NPTH
 #endif
 
-#ifdef HAVE_NPTH
+#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_GNUTLS
+#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 "util.h"
 #include "i18n.h"
@@ -115,10 +117,10 @@ struct srventry
 #endif/*!USE_DNS_SRV*/
 
 
-#ifdef HAVE_NPTH
-# define my_select(a,b,c,d,e)  pth_select ((a), (b), (c), (d), (e))
-# define my_connect(a,b,c)     pth_connect ((a), (b), (c))
-# define my_accept(a,b,c)      pth_accept ((a), (b), (c))
+#ifdef USE_NPTH
+# define my_select(a,b,c,d,e)  npth_select ((a), (b), (c), (d), (e))
+# define my_connect(a,b,c)     npth_connect ((a), (b), (c))
+# 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_connect(a,b,c)     connect ((a), (b), (c))
@@ -154,17 +156,27 @@ typedef unsigned long longcounter_t;
 # define counter_strtoul(a) strtoul ((a), NULL, 10)
 #endif
 
-#ifndef HTTP_USE_GNUTLS
-typedef void * gnutls_session_t;
+#if HTTP_USE_NTBTLS
+typedef ntbtls_t         tls_session_t;
+# define USE_TLS 1
+#elif HTTP_USE_GNUTLS
+typedef gnutls_session_t tls_session_t;
+# define USE_TLS 1
+#else
+typedef void *tls_session_t;
+# undef USE_TLS
 #endif
 
 static gpg_err_code_t do_parse_uri (parsed_uri_t uri, int only_local_part,
-                                    int no_scheme_check);
+                                    int no_scheme_check, int force_tls);
+static gpg_error_t parse_uri (parsed_uri_t *ret_uri, const char *uri,
+                              int no_scheme_check, int force_tls);
 static int remove_escapes (char *string);
 static int insert_escapes (char *buffer, const char *string,
                            const char *special);
 static uri_tuple_t parse_tuple (char *string);
-static gpg_error_t send_request (http_t hd, const char *auth,const char *proxy,
+static gpg_error_t send_request (http_t hd, const char *httphost,
+                                 const char *auth,const char *proxy,
                                 const char *srvtag,strlist_t headers);
 static char *build_rel_path (parsed_uri_t uri);
 static gpg_error_t parse_response (http_t hd);
@@ -202,22 +214,39 @@ struct cookie_s
   /* Socket object or NULL if already closed. */
   my_socket_t sock;
 
-  /* TLS session context or NULL if not used. */
-  gnutls_session_t tls_session;
+  /* 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;
   unsigned int content_length_valid:1;
-
-  /* Flag to communicate with the close handler. */
-  unsigned int keep_socket:1;
 };
 typedef struct cookie_s *cookie_t;
 
+/* The session object. */
+struct http_session_s
+{
+  int refcount;    /* Number of references to this object.  */
 #ifdef HTTP_USE_GNUTLS
-static gpg_error_t (*tls_callback) (http_t, gnutls_session_t, int);
+  gnutls_certificate_credentials_t certcred;
 #endif /*HTTP_USE_GNUTLS*/
+#ifdef USE_TLS
+  tls_session_t tls_session;
+  struct {
+    int done;      /* Verifciation has been done.  */
+    int rc;        /* TLS verification return code.  */
+    unsigned int status; /* Verification status.  */
+  } verify;
+  char *servername; /* Malloced server name.  */
+#endif /*USE_TLS*/
+  /* A callback function to log details of TLS certifciates.  */
+  void (*cert_log_cb) (http_session_t, gpg_error_t, const char *,
+                       const void **, size_t *);
+};
 
 
 /* An object to save header lines. */
@@ -241,7 +270,7 @@ struct http_context_s
   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. */
@@ -251,6 +280,12 @@ struct http_context_s
 };
 
 
+/* The global callback for the verification fucntion.  */
+static gpg_error_t (*tls_callback) (http_t, http_session_t, int);
+
+/* The list of files with trusted CA certificates.  */
+static strlist_t tls_ca_certlist;
+
 
 \f
 #if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
@@ -303,7 +338,7 @@ init_sockets (void)
 /* Create a new socket object.  Returns NULL and closes FD if not
    enough memory is available.  */
 static my_socket_t
-my_socket_new (int fd)
+_my_socket_new (int lnr, int fd)
 {
   my_socket_t so;
 
@@ -317,46 +352,71 @@ my_socket_new (int fd)
     }
   so->fd = fd;
   so->refcount = 1;
-  /* log_debug ("my_socket_new(%d): object %p for fd %d created\n", */
+  /* log_debug ("http.c:socket_new(%d): object %p for fd %d created\n", */
   /*            lnr, so, so->fd); */
+  (void)lnr;
   return so;
 }
-/* #define my_socket_new(a) _my_socket_new ((a),__LINE__) */
+#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 (my_socket_t so)
+_my_socket_ref (int lnr, my_socket_t so)
 {
   so->refcount++;
-  /* log_debug ("my_socket_ref(%d): object %p for fd %d refcount now %d\n", */
+  /* log_debug ("http.c:socket_ref(%d) object %p for fd %d refcount now %d\n", */
   /*            lnr, so, so->fd, so->refcount); */
+  (void)lnr;
   return so;
 }
-/* #define my_socket_ref(a) _my_socket_ref ((a),__LINE__) */
+#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 (my_socket_t so)
+_my_socket_unref (int lnr, my_socket_t so,
+                  void (*preclose)(void*), void *preclosearg)
 {
   if (so)
     {
       so->refcount--;
-      /* log_debug ("my_socket_unref(%d): object %p for fd %d ref now %d\n", */
+      /* log_debug ("http.c:socket_unref(%d): object %p for fd %d ref now %d\n", */
       /*            lnr, so, so->fd, so->refcount); */
+      (void)lnr;
       if (!so->refcount)
         {
+          if (preclose)
+            preclose (preclosearg);
           sock_close (so->fd);
           xfree (so);
         }
     }
 }
-/* #define my_socket_unref(a) _my_socket_unref ((a),__LINE__) */
+#define my_socket_unref(a,b,c) _my_socket_unref (__LINE__,(a),(b),(c))
+
+
+#if defined (USE_NPTH) && defined(HTTP_USE_GNUTLS)
+static ssize_t
+my_npth_read (gnutls_transport_ptr_t ptr, void *buffer, size_t size)
+{
+  my_socket_t sock = ptr;
+  return npth_read (sock->fd, buffer, size);
+}
+static ssize_t
+my_npth_write (gnutls_transport_ptr_t ptr, const void *buffer, size_t size)
+{
+  my_socket_t sock = ptr;
+  return npth_write (sock->fd, buffer, size);
+}
+#endif /*USE_NPTH && HTTP_USE_GNUTLS*/
+
 
 
+\f
 /* This notification function is called by estream whenever stream is
-   closed.  Its purpose is to mark the the closing in the handle so
+   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.  */
@@ -380,13 +440,13 @@ fp_onclose_notification (estream_t stream, void *opaque)
  */
 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[] =
     "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);
@@ -399,6 +459,7 @@ 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 )
     {
@@ -414,6 +475,7 @@ make_header_line (const char *prefix, const char *suffix,
       *p++ = '=';
       *p++ = '=';
     }
+  *p = 0;
   strcpy (p, suffix);
   return buffer;
 }
@@ -421,25 +483,218 @@ make_header_line (const char *prefix, const char *suffix,
 
 
 \f
+/* Register a non-standard global TLS callback function.  If no
+   verification is desired a callback needs to be registered which
+   always returns NULL.  */
 void
-http_register_tls_callback ( gpg_error_t (*cb) (http_t, void *, int) )
+http_register_tls_callback (gpg_error_t (*cb)(http_t, http_session_t, int))
 {
-#ifdef HTTP_USE_GNUTLS
-  tls_callback = (gpg_error_t (*) (http_t, gnutls_session_t, int))cb;
-#else
-  (void)cb;
-#endif
+  tls_callback = cb;
 }
 
 
+/* Register a CA certificate for future use.  The certificate is
+   expected to be in FNAME.  PEM format is assume if FNAME has a
+   suffix of ".pem".  If FNAME is NULL the list of CA files is
+   removed.  */
+void
+http_register_tls_ca (const char *fname)
+{
+  strlist_t sl;
+
+  if (!fname)
+    {
+      free_strlist (tls_ca_certlist);
+      tls_ca_certlist = NULL;
+    }
+  else
+    {
+      sl = add_to_strlist (&tls_ca_certlist, fname);
+      if (*sl->d && !strcmp (sl->d + strlen (sl->d) - 4, ".pem"))
+        sl->flags = 1;
+    }
+}
+
+
+/* Release a session.  Take care not to release it while it is being
+   used by a http context object.  */
+static void
+session_unref (int lnr, http_session_t sess)
+{
+  if (!sess)
+    return;
+
+  sess->refcount--;
+  /* log_debug ("http.c:session_unref(%d): sess %p ref now %d\n", */
+  /*            lnr, sess, sess->refcount); */
+  (void)lnr;
+  if (sess->refcount)
+    return;
 
-/* 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.  */
+#ifdef USE_TLS
+# ifdef HTTP_USE_GNUTLS
+  if (sess->tls_session)
+    {
+      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);
+#endif /*USE_TLS*/
+
+  xfree (sess);
+}
+#define http_session_unref(a) session_unref (__LINE__, (a))
+
+void
+http_session_release (http_session_t sess)
+{
+  http_session_unref (sess);
+}
+
+
+/* Create a new session object which is currently used to enable TLS
+   support.  It may eventually allow reusing existing connections.  */
+gpg_error_t
+http_session_new (http_session_t *r_session, const char *tls_priority)
+{
+  gpg_error_t err;
+  http_session_t sess;
+
+  *r_session = NULL;
+
+  sess = xtrycalloc (1, sizeof *sess);
+  if (!sess)
+    return gpg_error_from_syserror ();
+  sess->refcount = 1;
+
+#if HTTP_USE_NTBTLS
+  {
+    (void)tls_priority;
+
+    err = ntbtls_new (&sess->tls_session, NTBTLS_CLIENT);
+    if (err)
+      {
+        log_error ("ntbtls_new failed: %s\n", gpg_strerror (err));
+        goto leave;
+      }
+  }
+#elif HTTP_USE_GNUTLS
+  {
+    const char *errpos;
+    int rc;
+    strlist_t sl;
+
+    rc = gnutls_certificate_allocate_credentials (&sess->certcred);
+    if (rc < 0)
+      {
+        log_error ("gnutls_certificate_allocate_credentials failed: %s\n",
+                   gnutls_strerror (rc));
+        err = gpg_error (GPG_ERR_GENERAL);
+        goto leave;
+      }
+
+    for (sl = tls_ca_certlist; sl; sl = sl->next)
+      {
+        rc = gnutls_certificate_set_x509_trust_file
+          (sess->certcred, sl->d,
+           (sl->flags & 1)? GNUTLS_X509_FMT_PEM : GNUTLS_X509_FMT_DER);
+        if (rc < 0)
+          log_info ("setting CA from file '%s' failed: %s\n",
+                    sl->d, gnutls_strerror (rc));
+      }
+
+    rc = gnutls_init (&sess->tls_session, GNUTLS_CLIENT);
+    if (rc < 0)
+      {
+        log_error ("gnutls_init failed: %s\n", gnutls_strerror (rc));
+        err = gpg_error (GPG_ERR_GENERAL);
+        goto leave;
+      }
+    /* A new session has the transport ptr set to (void*(-1), we need
+       it to be NULL.  */
+    gnutls_transport_set_ptr (sess->tls_session, NULL);
+
+    rc = gnutls_priority_set_direct (sess->tls_session,
+                                     tls_priority? tls_priority : "NORMAL",
+                                     &errpos);
+    if (rc < 0)
+      {
+        log_error ("gnutls_priority_set_direct failed at '%s': %s\n",
+                   errpos, gnutls_strerror (rc));
+        err = gpg_error (GPG_ERR_GENERAL);
+        goto leave;
+      }
+
+    rc = gnutls_credentials_set (sess->tls_session,
+                                 GNUTLS_CRD_CERTIFICATE, sess->certcred);
+    if (rc < 0)
+      {
+        log_error ("gnutls_credentials_set failed: %s\n", gnutls_strerror (rc));
+        err = gpg_error (GPG_ERR_GENERAL);
+        goto leave;
+      }
+  }
+#else /*!HTTP_USE_GNUTLS*/
+  {
+    (void)tls_priority;
+  }
+#endif /*!HTTP_USE_GNUTLS*/
+
+  /* log_debug ("http.c:session_new: sess %p created\n", sess); */
+  err = 0;
+
+#if USE_TLS
+ leave:
+#endif /*USE_TLS*/
+  if (err)
+    http_session_unref (sess);
+  else
+    *r_session = sess;
+
+  return err;
+}
+
+
+/* Increment the reference count for session SESS.  Passing NULL for
+   SESS is allowed. */
+http_session_t
+http_session_ref (http_session_t sess)
+{
+  if (sess)
+    {
+      sess->refcount++;
+      /* log_debug ("http.c:session_ref: sess %p ref now %d\n", sess, */
+      /*            sess->refcount); */
+    }
+  return sess;
+}
+
+
+void
+http_session_set_log_cb (http_session_t sess,
+                         void (*cb)(http_session_t, gpg_error_t,
+                                    const char *hostname,
+                                    const void **certs, size_t *certlens))
+{
+  sess->cert_log_cb = cb;
+}
+
+
+
+\f
+/* Start a HTTP retrieval and on success store at R_HD a context
+   pointer for completing the request and to wait for the response.
+   If HTTPHOST is not NULL it is used hor the Host header instead of a
+   Host header derived from the URL. */
 gpg_error_t
 http_open (http_t *r_hd, http_req_t reqtype, const char *url,
+           const char *httphost,
            const char *auth, unsigned int flags, const char *proxy,
-           void *tls_context, const char *srvtag, strlist_t headers)
+           http_session_t session, const char *srvtag, strlist_t headers)
 {
   gpg_error_t err;
   http_t hd;
@@ -455,19 +710,20 @@ http_open (http_t *r_hd, http_req_t reqtype, const char *url,
     return gpg_error_from_syserror ();
   hd->req_type = reqtype;
   hd->flags = flags;
-  hd->tls_context = tls_context;
+  hd->session = http_session_ref (session);
 
-  err = http_parse_uri (&hd->uri, url, 0);
+  err = parse_uri (&hd->uri, url, 0, !!(flags & HTTP_FLAG_FORCE_TLS));
   if (!err)
-    err = send_request (hd, auth, proxy, srvtag, headers);
+    err = send_request (hd, httphost, auth, proxy, srvtag, headers);
 
   if (err)
     {
-      my_socket_unref (hd->sock);
+      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
@@ -528,7 +784,7 @@ http_raw_connect (http_t *r_hd, const char *server, unsigned short port,
   if (!hd->fp_write)
     {
       err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
-      my_socket_unref (cookie->sock);
+      my_socket_unref (cookie->sock, NULL, NULL);
       xfree (cookie);
       goto leave;
     }
@@ -545,7 +801,7 @@ http_raw_connect (http_t *r_hd, const char *server, unsigned short port,
   if (!hd->fp_read)
     {
       err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
-      my_socket_unref (cookie->sock);
+      my_socket_unref (cookie->sock, NULL, NULL);
       xfree (cookie);
       goto leave;
     }
@@ -564,7 +820,7 @@ http_raw_connect (http_t *r_hd, const char *server, unsigned short port,
         es_fclose (hd->fp_read);
       if (hd->fp_write)
         es_fclose (hd->fp_write);
-      my_socket_unref (hd->sock);
+      my_socket_unref (hd->sock, NULL, NULL);
       xfree (hd);
     }
   else
@@ -621,15 +877,16 @@ http_wait_response (http_t hd)
   if (!cookie)
     return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
   cookie->sock = my_socket_ref (hd->sock);
-  if (hd->uri->use_tls)
-    cookie->tls_session = hd->tls_context;
+  cookie->session = http_session_ref (hd->session);
+  cookie->use_tls = hd->uri->use_tls;
 
   hd->read_cookie = cookie;
   hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
   if (!hd->fp_read)
     {
       err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
-      my_socket_unref (cookie->sock);
+      my_socket_unref (cookie->sock, NULL, NULL);
+      http_session_unref (cookie->session);
       xfree (cookie);
       hd->read_cookie = NULL;
       return err;
@@ -651,12 +908,13 @@ http_wait_response (http_t hd)
 gpg_error_t
 http_open_document (http_t *r_hd, const char *document,
                     const char *auth, unsigned int flags, const char *proxy,
-                    void *tls_context, const char *srvtag, strlist_t headers)
+                    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, srvtag, headers);
+  err = http_open (r_hd, HTTP_REQ_GET, document, NULL, auth, flags,
+                   proxy, session, srvtag, headers);
   if (err)
     return err;
 
@@ -681,11 +939,12 @@ http_close (http_t hd, int keep_read_stream)
     es_onclose (hd->fp_write, 0, fp_onclose_notification, hd);
 
   /* Now we can close the streams.  */
-  my_socket_unref (hd->sock);
+  my_socket_unref (hd->sock, NULL, NULL);
   if (hd->fp_read && !keep_read_stream)
     es_fclose (hd->fp_read);
   if (hd->fp_write)
     es_fclose (hd->fp_write);
+  http_session_unref (hd->session);
   http_release_parsed_uri (hd->uri);
   while (hd->headers)
     {
@@ -717,17 +976,29 @@ 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:
 
-\f
-/*
- * Parse an URI and put the result into the newly allocated RET_URI.
- * On success the caller must use 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.
+     (NULL) := Only check whether TLS is is use.  Returns an
+               unspecified string if TLS is in use.  That string may
+               even be the empty string.
  */
-gpg_error_t
-http_parse_uri (parsed_uri_t *ret_uri, const char *uri,
-                int no_scheme_check)
+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;
 
@@ -735,7 +1006,7 @@ http_parse_uri (parsed_uri_t *ret_uri, const char *uri,
   if (!*ret_uri)
     return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
   strcpy ((*ret_uri)->buffer, uri);
-  ec = do_parse_uri (*ret_uri, 0, no_scheme_check);
+  ec = do_parse_uri (*ret_uri, 0, no_scheme_check, force_tls);
   if (ec)
     {
       xfree (*ret_uri);
@@ -744,6 +1015,22 @@ http_parse_uri (parsed_uri_t *ret_uri, const char *uri,
   return gpg_err_make (default_errsource, ec);
 }
 
+
+/*
+ * Parse an URI and put the result into the newly allocated RET_URI.
+ * 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,
+                int no_scheme_check)
+{
+  return parse_uri (ret_uri, uri, no_scheme_check, 0);
+}
+
+
 void
 http_release_parsed_uri (parsed_uri_t uri)
 {
@@ -762,7 +1049,8 @@ http_release_parsed_uri (parsed_uri_t uri)
 
 
 static gpg_err_code_t
-do_parse_uri (parsed_uri_t uri, int only_local_part, int no_scheme_check)
+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;
@@ -778,6 +1066,7 @@ do_parse_uri (parsed_uri_t uri, int only_local_part, int no_scheme_check)
   uri->use_tls = 0;
   uri->is_http = 0;
   uri->opaque = 0;
+  uri->v6lit = 0;
 
   /* A quick validity check. */
   if (strspn (p, VALID_URI_CHARS) != n)
@@ -792,24 +1081,26 @@ do_parse_uri (parsed_uri_t uri, int only_local_part, int no_scheme_check)
       for (pp=p; *pp; pp++)
        *pp = tolower (*(unsigned char*)pp);
       uri->scheme = p;
-      if (!strcmp (uri->scheme, "http"))
+      if (!strcmp (uri->scheme, "http") && !force_tls)
         {
           uri->port = 80;
           uri->is_http = 1;
         }
-      else if (!strcmp (uri->scheme, "hkp"))
+      else if (!strcmp (uri->scheme, "hkp") && !force_tls)
         {
           uri->port = 11371;
           uri->is_http = 1;
         }
-#ifdef HTTP_USE_GNUTLS
-      else if (!strcmp (uri->scheme, "https") || !strcmp (uri->scheme,"hkps"))
+#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
+#endif /*USE_TLS*/
       else if (!no_scheme_check)
        return GPG_ERR_INV_URI; /* Unsupported scheme */
 
@@ -838,6 +1129,7 @@ do_parse_uri (parsed_uri_t uri, int only_local_part, int no_scheme_check)
              *p3++ = '\0';
              /* worst case, uri->host should have length 0, points to \0 */
              uri->host = p + 1;
+              uri->v6lit = 1;
              p = p3;
            }
          else
@@ -957,16 +1249,41 @@ remove_escapes (char *string)
 }
 
 
+/* 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)
 {
+  int forms = !special;
   const unsigned char *s;
   size_t n = 0;
 
+  if (forms)
+    special = "%;?&=";
+
   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;
@@ -998,7 +1315,8 @@ insert_escapes (char *buffer, const char *string,
    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)
 {
@@ -1019,7 +1337,8 @@ http_escape_string (const char *string, const char *specials)
    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. */
+   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)
 {
@@ -1037,7 +1356,6 @@ http_escape_data (const void *data, size_t datalen, const char *specials)
 }
 
 
-
 static uri_tuple_t
 parse_tuple (char *string)
 {
@@ -1076,15 +1394,41 @@ parse_tuple (char *string)
 }
 
 
+/* Return true if STRING is likely "hostname:port" or only "hostname".  */
+static int
+is_hostname_port (const char *string)
+{
+  int colons = 0;
+
+  if (!string || !*string)
+    return 0;
+  for (; *string; string++)
+    {
+      if (*string == ':')
+        {
+          if (colons)
+            return 0;
+          if (!string[1])
+            return 0;
+          colons++;
+        }
+      else if (!colons && strchr (" \t\f\n\v_@[]/", *string))
+        return 0; /* Invalid characters in hostname. */
+      else if (colons && !digitp (string))
+        return 0; /* Not a digit in the port.  */
+    }
+  return 1;
+}
+
+
 /*
  * Send a HTTP request to the server
  * Returns 0 if the request was successful
  */
 static gpg_error_t
-send_request (http_t hd, const char *auth,
+send_request (http_t hd, const char *httphost, const char *auth,
              const char *proxy, const char *srvtag, strlist_t headers)
 {
-  gnutls_session_t tls_session;
   gpg_error_t err;
   const char *server;
   char *request, *p;
@@ -1095,16 +1439,57 @@ send_request (http_t hd, const char *auth,
   int sock;
   int hnf;
 
-  tls_session = hd->tls_context;
-  if (hd->uri->use_tls && !tls_session)
+  if (hd->uri->use_tls && !hd->session)
     {
-      log_error ("TLS requested but no GNUTLS context provided\n");
+      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*/
 
   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))
@@ -1116,7 +1501,26 @@ send_request (http_t hd, const char *auth,
       if (proxy)
        http_proxy = proxy;
 
-      err = http_parse_uri (&uri, http_proxy, 0);
+      err = parse_uri (&uri, http_proxy, 1, 0);
+      if (gpg_err_code (err) == GPG_ERR_INV_URI
+          && is_hostname_port (http_proxy))
+        {
+          /* Retry assuming a "hostname:port" string.  */
+          char *tmpname = strconcat ("http://", http_proxy, NULL);
+          if (tmpname && !parse_uri (&uri, tmpname, 0, 0))
+            err = 0;
+          xfree (tmpname);
+        }
+
+      if (err)
+        ;
+      else if (!strcmp (uri->scheme, "http") || !strcmp (uri->scheme, "socks4"))
+        ;
+      else if (!strcmp (uri->scheme, "socks5h"))
+        err = gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
+      else
+        err = gpg_err_make (default_errsource, GPG_ERR_INV_URI);
+
       if (err)
        {
          log_error ("invalid HTTP proxy (%s): %s\n",
@@ -1168,36 +1572,87 @@ send_request (http_t hd, const char *auth,
 
 
 
-#ifdef HTTP_USE_GNUTLS
+#if HTTP_USE_NTBTLS
+  if (hd->uri->use_tls)
+    {
+      my_socket_ref (hd->sock);
+
+      while ((err = ntbtls_handshake (hd->session->tls_session)))
+        {
+          switch (err)
+            {
+            default:
+              log_info ("TLS handshake failed: %s <%s>\n",
+                        gpg_strerror (err), gpg_strsource (err));
+              xfree (proxy_authstr);
+              return err;
+            }
+        }
+
+      hd->session->verify.done = 0;
+      if (tls_callback)
+        err = tls_callback (hd, hd->session, 0);
+      else
+        err = http_verify_server_credentials (hd->session);
+      if (err)
+        {
+          log_info ("TLS connection authentication failed: %s <%s>\n",
+                    gpg_strerror (err), gpg_strsource (err));
+          xfree (proxy_authstr);
+          return err;
+        }
+    }
+#elif HTTP_USE_GNUTLS
   if (hd->uri->use_tls)
     {
       int rc;
 
       my_socket_ref (hd->sock);
-      gnutls_transport_set_ptr (tls_session,
-                                (gnutls_transport_ptr_t)(hd->sock->fd));
+      gnutls_transport_set_ptr (hd->session->tls_session, hd->sock);
+#ifdef USE_NPTH
+      gnutls_transport_set_pull_function (hd->session->tls_session,
+                                          my_npth_read);
+      gnutls_transport_set_push_function (hd->session->tls_session,
+                                          my_npth_write);
+#endif
+
       do
         {
-          rc = gnutls_handshake (tls_session);
+          rc = gnutls_handshake (hd->session->tls_session);
         }
       while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN);
       if (rc < 0)
         {
-          log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc));
+          if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED
+              || rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
+            {
+              gnutls_alert_description_t alertno;
+              const char *alertstr;
+
+              alertno = gnutls_alert_get (hd->session->tls_session);
+              alertstr = gnutls_alert_get_name (alertno);
+              log_info ("TLS handshake failed: %s (alert %d)\n",
+                        alertstr, (int)alertno);
+              if (alertno == GNUTLS_A_UNRECOGNIZED_NAME && server)
+                log_info ("  (sent server name '%s')\n", server);
+            }
+          else
+            log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc));
           xfree (proxy_authstr);
           return gpg_err_make (default_errsource, GPG_ERR_NETWORK);
         }
 
+      hd->session->verify.done = 0;
       if (tls_callback)
+        err = tls_callback (hd, hd->session, 0);
+      else
+        err = http_verify_server_credentials (hd->session);
+      if (err)
         {
-          err = tls_callback (hd, tls_session, 0);
-          if (err)
-            {
-              log_info ("TLS connection authentication failed: %s\n",
-                        gpg_strerror (err));
-              xfree (proxy_authstr);
-              return err;
-            }
+          log_info ("TLS connection authentication failed: %s\n",
+                    gpg_strerror (err));
+          xfree (proxy_authstr);
+          return err;
         }
     }
 #endif /*HTTP_USE_GNUTLS*/
@@ -1222,7 +1677,7 @@ send_request (http_t hd, const char *auth,
           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);
@@ -1241,12 +1696,14 @@ send_request (http_t hd, const char *auth,
 
   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 : "");
     }
@@ -1259,12 +1716,14 @@ send_request (http_t hd, const char *auth,
       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);
@@ -1276,6 +1735,7 @@ send_request (http_t hd, const char *auth,
       return err;
     }
 
+  /* log_debug ("request:\n%s\nEND request\n", request); */
 
   /* First setup estream so that we can write even the first line
      using estream.  This is also required for the sake of gnutls. */
@@ -1290,14 +1750,14 @@ send_request (http_t hd, const char *auth,
       }
     cookie->sock = my_socket_ref (hd->sock);
     hd->write_cookie = cookie;
-    if (hd->uri->use_tls)
-      cookie->tls_session = tls_session;
+    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);
+        my_socket_unref (cookie->sock, NULL, NULL);
         xfree (cookie);
         hd->write_cookie = NULL;
       }
@@ -1486,8 +1946,8 @@ 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.  */
 const char *
@@ -1502,6 +1962,29 @@ 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.
@@ -1538,7 +2021,7 @@ parse_response (http_t hd)
        return GPG_ERR_EOF;
 
       if ((hd->flags & HTTP_FLAG_LOG_RESP))
-        log_info ("RESP: `%.*s'\n",
+        log_info ("RESP: '%.*s'\n",
                   (int)strlen(line)-(*line&&line[1]?2:0),line);
     }
   while (!*line);
@@ -1584,7 +2067,7 @@ parse_response (http_t hd)
       if ((*line == '\r' && line[1] == '\n') || *line == '\n')
        *line = 0;
       if ((hd->flags & HTTP_FLAG_LOG_RESP))
-        log_info ("RESP: `%.*s'\n",
+        log_info ("RESP: '%.*s'\n",
                   (int)strlen(line)-(*line&&line[1]?2:0),line);
       if (*line)
         {
@@ -1695,14 +2178,13 @@ connect_server (const char *server, unsigned short port,
   int sock = -1;
   int srvcount = 0;
   int hostfound = 0;
+  int anyhostaddr = 0;
   int srv, connected;
   int last_errno = 0;
   struct srventry *serverlist = NULL;
 #ifdef HAVE_W32_SYSTEM
   unsigned long inaddr;
 #endif
-  /* Not currently using the flags */
-  (void)flags;
 
   *r_host_not_found = 0;
 #ifdef HAVE_W32_SYSTEM
@@ -1785,6 +2267,11 @@ connect_server (const char *server, unsigned short port,
 
       for (ai = res; ai && !connected; ai = ai->ai_next)
         {
+          if (ai->ai_family == AF_INET && (flags & HTTP_FLAG_IGNORE_IPv4))
+            continue;
+          if (ai->ai_family == AF_INET6 && (flags & HTTP_FLAG_IGNORE_IPv6))
+            continue;
+
           if (sock != -1)
             sock_close (sock);
           sock = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol);
@@ -1798,6 +2285,7 @@ connect_server (const char *server, unsigned short port,
               return -1;
             }
 
+          anyhostaddr = 1;
           if (my_connect (sock, ai->ai_addr, ai->ai_addrlen))
             last_errno = errno;
           else
@@ -1826,7 +2314,7 @@ connect_server (const char *server, unsigned short port,
       sock = socket (host->h_addrtype, SOCK_STREAM, 0);
       if (sock == -1)
         {
-          log_error (_("error creating socket: %s\n"), strerror (errno));
+          log_error ("error creating socket: %s\n", strerror (errno));
           xfree (serverlist);
           return -1;
         }
@@ -1834,7 +2322,7 @@ connect_server (const char *server, unsigned short port,
       addr.sin_family = host->h_addrtype;
       if (addr.sin_family != AF_INET)
        {
-         log_error ("unknown address family for `%s'\n",
+         log_error ("unknown address family for '%s'\n",
                      serverlist[srv].target);
           xfree (serverlist);
          return -1;
@@ -1842,7 +2330,7 @@ connect_server (const char *server, unsigned short port,
       addr.sin_port = htons (serverlist[srv].port);
       if (host->h_length != 4)
         {
-          log_error ("illegal address length for `%s'\n",
+          log_error ("illegal address length for '%s'\n",
                      serverlist[srv].target);
           xfree (serverlist);
           return -1;
@@ -1851,6 +2339,7 @@ connect_server (const char *server, unsigned short port,
       /* Try all A records until one responds. */
       for (i = 0; host->h_addr_list[i] && !connected; i++)
         {
+          anyhostaddr = 1;
           memcpy (&addr.sin_addr, host->h_addr_list[i], host->h_length);
           if (my_connect (sock, (struct sockaddr *) &addr, sizeof (addr)))
             last_errno = errno;
@@ -1867,17 +2356,23 @@ connect_server (const char *server, unsigned short port,
 
   if (!connected)
     {
+      if (!hostfound)
+        log_error ("can't connect to '%s': %s\n",
+                   server, "host not found");
+      else if (!anyhostaddr)
+        log_error ("can't connect to '%s': %s\n",
+                   server, "no IP address for host");
+      else
+        {
 #ifdef HAVE_W32_SYSTEM
-      log_error ("can't connect to `%s': %s%sec=%d\n",
-                   server,
-                   hostfound? "":_("host not found"),
-                   hostfound? "":" - ", (int)WSAGetLastError());
+        log_error ("can't connect to '%s': ec=%d\n",
+                   server, (int)WSAGetLastError());
 #else
-      log_error ("can't connect to `%s': %s\n",
-                 server,
-                 hostfound? strerror (last_errno):"host not found");
+        log_error ("can't connect to '%s': %s\n",
+                   server, strerror (last_errno));
 #endif
-      if (!hostfound)
+        }
+      if (!hostfound || (hostfound && !anyhostaddr))
         *r_host_not_found = 1;
       if (sock != -1)
        sock_close (sock);
@@ -1897,16 +2392,22 @@ write_server (int sock, const char *data, size_t length)
   nleft = length;
   while (nleft > 0)
     {
-#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_NPTH)
+#if defined(HAVE_W32_SYSTEM)
+# if defined(USE_NPTH)
+      npth_unprotect ();
+# endif
       nwritten = send (sock, data, nleft, 0);
+# if defined(USE_NPTH)
+      npth_protect ();
+# endif
       if ( nwritten == SOCKET_ERROR )
         {
           log_info ("network write failed: ec=%d\n", (int)WSAGetLastError ());
           return gpg_error (GPG_ERR_NETWORK);
         }
-#else /*!HAVE_W32_SYSTEM || HAVE_NPTH*/
-# ifdef HAVE_NPTH
-      nwritten = pth_write (sock, data, nleft);
+#else /*!HAVE_W32_SYSTEM*/
+# ifdef USE_NPTH
+      nwritten = npth_write (sock, data, nleft);
 # else
       nwritten = write (sock, data, nleft);
 # endif
@@ -1926,7 +2427,7 @@ write_server (int sock, const char *data, size_t length)
          log_info ("network write failed: %s\n", strerror (errno));
          return gpg_error_from_syserror ();
        }
-#endif /*!HAVE_W32_SYSTEM || HAVE_NPTH*/
+#endif /*!HAVE_W32_SYSTEM*/
       nleft -= nwritten;
       data += nwritten;
     }
@@ -1952,10 +2453,10 @@ cookie_read (void *cookie, void *buffer, size_t size)
     }
 
 #ifdef HTTP_USE_GNUTLS
-  if (c->tls_session)
+  if (c->use_tls && c->session && c->session->tls_session)
     {
     again:
-      nread = gnutls_record_recv (c->tls_session, buffer, size);
+      nread = gnutls_record_recv (c->session->tls_session, buffer, size);
       if (nread < 0)
         {
           if (nread == GNUTLS_E_INTERRUPTED)
@@ -1981,14 +2482,25 @@ cookie_read (void *cookie, void *buffer, size_t size)
     {
       do
         {
-#ifdef HAVE_NPTH
-          nread = pth_read (c->sock->fd, buffer, size);
-#elif defined(HAVE_W32_SYSTEM)
+#ifdef HAVE_W32_SYSTEM
           /* Under Windows we need to use recv for a socket.  */
+# if defined(USE_NPTH)
+          npth_unprotect ();
+# endif
           nread = recv (c->sock->fd, buffer, size, 0);
-#else
+# if defined(USE_NPTH)
+          npth_protect ();
+# endif
+
+#else /*!HAVE_W32_SYSTEM*/
+
+# ifdef USE_NPTH
+          nread = npth_read (c->sock->fd, buffer, size);
+# else
           nread = read (c->sock->fd, buffer, size);
-#endif
+# endif
+
+#endif /*!HAVE_W32_SYSTEM*/
         }
       while (nread == -1 && errno == EINTR);
     }
@@ -2006,18 +2518,20 @@ cookie_read (void *cookie, void *buffer, size_t size)
 
 /* Write handler for estream.  */
 static ssize_t
-cookie_write (void *cookie, const void *buffer, size_t size)
+cookie_write (void *cookie, const void *buffer_arg, size_t size)
 {
+  const char *buffer = buffer_arg;
   cookie_t c = cookie;
   int nwritten = 0;
 
 #ifdef HTTP_USE_GNUTLS
-  if (c->tls_session)
+  if (c->use_tls && c->session && c->session->tls_session)
     {
       int nleft = size;
       while (nleft > 0)
         {
-          nwritten = gnutls_record_send (c->tls_session, buffer, nleft);
+          nwritten = gnutls_record_send (c->session->tls_session,
+                                         buffer, nleft);
           if (nwritten <= 0)
             {
               if (nwritten == GNUTLS_E_INTERRUPTED)
@@ -2055,6 +2569,31 @@ cookie_write (void *cookie, const void *buffer, size_t size)
   return nwritten;
 }
 
+
+#ifdef HTTP_USE_GNUTLS
+/* Wrapper for gnutls_bye used by my_socket_unref.  */
+static void
+send_gnutls_bye (void *opaque)
+{
+  tls_session_t tls_session = opaque;
+  int ret;
+
+ again:
+  do
+    ret = gnutls_bye (tls_session, GNUTLS_SHUT_RDWR);
+  while (ret == GNUTLS_E_INTERRUPTED);
+  if (ret == GNUTLS_E_AGAIN)
+    {
+      struct timeval tv;
+
+      tv.tv_sec = 0;
+      tv.tv_usec = 50000;
+      my_select (0, NULL, NULL, NULL, &tv);
+      goto again;
+    }
+}
+#endif /*HTTP_USE_GNUTLS*/
+
 /* Close handler for estream.  */
 static int
 cookie_close (void *cookie)
@@ -2065,182 +2604,162 @@ cookie_close (void *cookie)
     return 0;
 
 #ifdef HTTP_USE_GNUTLS
-  if (c->tls_session && !c->keep_socket)
-    {
-      gnutls_bye (c->tls_session, GNUTLS_SHUT_RDWR);
-      my_socket_unref (c->sock);
-    }
+  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->sock && !c->keep_socket)
-    my_socket_unref (c->sock);
+    if (c->sock)
+      my_socket_unref (c->sock, NULL, NULL);
 
+  if (c->session)
+    http_session_unref (c->session);
   xfree (c);
   return 0;
 }
 
 
-\f
-/**** Test code ****/
-#ifdef TEST
-
-#ifdef HTTP_USE_GNUTLS
-static gpg_error_t
-verify_callback (http_t hd, void *tls_context, int reserved)
-{
-  log_info ("verification of certificates skipped\n");
-  return 0;
-}
-#endif /*HTTP_USE_GNUTLS*/
-
-
-/* static void */
-/* my_gnutls_log (int level, const char *text) */
-/* { */
-/*   fprintf (stderr, "gnutls:L%d: %s", level, text); */
-/* } */
 
-int
-main (int argc, char **argv)
+\f
+/* Verify the credentials of the server.  Returns 0 on success and
+   store the result in the session object.  */
+gpg_error_t
+http_verify_server_credentials (http_session_t sess)
 {
+#if HTTP_USE_NTBTLS
+  (void)sess;
+  return 0;  /* FIXME!! */
+#elif HTTP_USE_GNUTLS
+  static const char const errprefix[] = "TLS verification of peer failed";
   int rc;
-  parsed_uri_t uri;
-  uri_tuple_t r;
-  http_t hd;
-  int c;
-  gnutls_session_t tls_session = NULL;
-#ifdef HTTP_USE_GNUTLS
-  gnutls_certificate_credentials certcred;
-  const int certprio[] = { GNUTLS_CRT_X509, 0 };
-#endif /*HTTP_USE_GNUTLS*/
-  header_t hdr;
+  unsigned int status;
+  const char *hostname;
+  const gnutls_datum_t *certlist;
+  unsigned int certlistlen;
+  gnutls_x509_crt_t cert;
+  gpg_error_t err = 0;
+
+  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;
+
+        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*/
 
-#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); */
+      sess->verify.status = status;
+      if (!err)
+        err = gpg_error (GPG_ERR_GENERAL);
+    }
 
-  http_register_tls_callback (verify_callback);
-#endif /*HTTP_USE_GNUTLS*/
+  hostname = sess->servername;
+  if (!hostname || !strchr (hostname, '.'))
+    {
+      log_error ("%s: %s\n", errprefix, "hostname missing");
+      if (!err)
+        err = gpg_error (GPG_ERR_GENERAL);
+    }
 
-  rc = http_parse_uri (&uri, *argv, 1);
-  if (rc)
+  certlist = gnutls_certificate_get_peers (sess->tls_session, &certlistlen);
+  if (!certlistlen)
     {
-      log_error ("`%s': %s\n", *argv, gpg_strerror (rc));
-      return 1;
+      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;
     }
 
-  printf ("Scheme: %s\n", uri->scheme);
-  if (uri->opaque)
-    printf ("Value : %s\n", uri->path);
-  else
+  rc = gnutls_x509_crt_init (&cert);
+  if (rc < 0)
     {
-      printf ("Auth  : %s\n", uri->auth? uri->auth:"[none]");
-      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)
-        {
-          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');
-        }
-      for (r = uri->query; r; r = r->next)
-        {
-          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, 0, NULL, tls_session, NULL, NULL);
-  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*/
 
+/* 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;
 
-/*
-Local Variables:
-compile-command: "gcc -I.. -I../gl -DTEST -DHAVE_CONFIG_H -Wall -O2 -g -o http-test http.c -L. -lcommon -lgcrypt -lpth -lgnutls"
-End:
-*/
+  return NULL;
+}