Merge T3490-proposal1 into master
[gnupg.git] / dirmngr / http.c
index b74a9ef..8e778df 100644 (file)
@@ -2,7 +2,7 @@
  * 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
+ * Copyright (C) 2015-2017 g10 Code GmbH
  *
  * This file is part of GnuPG.
  *
@@ -31,7 +31,7 @@
  */
 
 /* 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
@@ -70,6 +70,7 @@
 # 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>
 
 #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"
+#include "http-common.h"
 
 
 #ifdef USE_NPTH
@@ -151,20 +154,27 @@ static int insert_escapes (char *buffer, const char *string,
 static uri_tuple_t parse_tuple (char *string);
 static gpg_error_t send_request (http_t hd, const char *httphost,
                                  const char *auth,const char *proxy,
-                                const char *srvtag,strlist_t headers);
+                                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 assuan_fd_t connect_server (const char *server, unsigned short port,
+static gpg_error_t connect_server (const char *server, unsigned short port,
                                    unsigned int flags, const char *srvtag,
-                                   int *r_host_not_found);
-static gpg_error_t write_server (int sock, const char *data, size_t length);
+                                   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 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
@@ -184,6 +194,7 @@ static es_cookie_io_functions_t cookie_functions =
     cookie_close
   };
 
+
 struct cookie_s
 {
   /* Socket object or NULL if already closed. */
@@ -202,9 +213,31 @@ struct cookie_s
 };
 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
   gnutls_certificate_credentials_t certcred;
@@ -221,6 +254,16 @@ struct http_session_s
   /* 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;
 };
 
 
@@ -234,9 +277,17 @@ 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
 {
+  unsigned long magic;
   unsigned int status_code;
   my_socket_t sock;
   unsigned int in_data:1;
@@ -255,6 +306,12 @@ 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);
 
@@ -330,9 +387,9 @@ _my_socket_new (int lnr, assuan_fd_t fd)
     }
   so->fd = fd;
   so->refcount = 1;
-  /* log_debug ("http.c:socket_new(%d): object %p for fd %d created\n", */
-  /*            lnr, so, so->fd); */
-  (void)lnr;
+  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))
@@ -342,9 +399,9 @@ static my_socket_t
 _my_socket_ref (int lnr, my_socket_t so)
 {
   so->refcount++;
-  /* log_debug ("http.c:socket_ref(%d) object %p for fd %d refcount now %d\n", */
-  /*            lnr, so, so->fd, so->refcount); */
-  (void)lnr;
+  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))
@@ -360,9 +417,10 @@ _my_socket_unref (int lnr, my_socket_t so,
   if (so)
     {
       so->refcount--;
-      /* log_debug ("http.c:socket_unref(%d): object %p for fd %d ref now %d\n", */
-      /*            lnr, so, so->fd, so->refcount); */
-      (void)lnr;
+      if (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)
@@ -399,6 +457,27 @@ my_gnutls_write (gnutls_transport_ptr_t ptr, const void *buffer, size_t size)
 #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
@@ -411,6 +490,7 @@ 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)
@@ -469,6 +549,15 @@ make_header_line (const char *prefix, const char *suffix,
 
 
 \f
+/* Set verbosity and debug mode for this module. */
+void
+http_set_verbose (int verbose, int debug)
+{
+  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.  */
@@ -495,6 +584,11 @@ http_register_tls_ca (const char *fname)
     }
   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;
@@ -528,7 +622,13 @@ close_tls_session (http_session_t sess)
 {
   if (sess->tls_session)
     {
-# ifdef HTTP_USE_GNUTLS
+# 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);
@@ -550,10 +650,12 @@ session_unref (int lnr, http_session_t sess)
   if (!sess)
     return;
 
+  log_assert (sess->magic == HTTP_SESSION_MAGIC);
+
   sess->refcount--;
-  /* log_debug ("http.c:session_unref(%d): sess %p ref now %d\n", */
-  /*            lnr, sess, sess->refcount); */
-  (void)lnr;
+  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;
 
@@ -561,6 +663,7 @@ session_unref (int lnr, http_session_t sess)
   close_tls_session (sess);
 #endif /*USE_TLS*/
 
+  sess->magic = 0xdeadbeef;
   xfree (sess);
 }
 #define http_session_unref(a) session_unref (__LINE__, (a))
@@ -577,10 +680,12 @@ http_session_release (http_session_t sess)
  * 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 *tls_priority,
-                  const char *intended_hostname, unsigned int flags)
+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;
@@ -590,11 +695,17 @@ http_session_new (http_session_t *r_session, const char *tls_priority,
   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)tls_priority;
+    (void)intended_hostname; /* Not needed because we do not preload
+                              * certificates.  */
 
     err = ntbtls_new (&sess->tls_session, NTBTLS_CLIENT);
     if (err)
@@ -602,12 +713,15 @@ http_session_new (http_session_t *r_session, const char *tls_priority,
         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)
@@ -618,13 +732,14 @@ http_session_new (http_session_t *r_session, const char *tls_priority,
         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
-        && intended_hostname
-        && !ascii_strcasecmp (intended_hostname,
-                              "hkps.pool.sks-keyservers.net"))
+    if (!tls_ca_certlist && is_hkps_pool)
       {
         char *pemname = make_filename_try (gnupg_datadir (),
                                            "sks-keyservers.netCA.pem", NULL);
@@ -657,10 +772,12 @@ http_session_new (http_session_t *r_session, const char *tls_priority,
               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 ((flags & HTTP_FLAG_TRUST_SYS))
+    if (add_system_cas)
       {
 #if GNUTLS_VERSION_NUMBER >= 0x030014
         static int shown;
@@ -688,7 +805,7 @@ http_session_new (http_session_t *r_session, const char *tls_priority,
     gnutls_transport_set_ptr (sess->tls_session, NULL);
 
     rc = gnutls_priority_set_direct (sess->tls_session,
-                                     tls_priority? tls_priority : "NORMAL",
+                                     "NORMAL",
                                      &errpos);
     if (rc < 0)
       {
@@ -707,13 +824,15 @@ http_session_new (http_session_t *r_session, const char *tls_priority,
         goto leave;
       }
   }
-#else /*!HTTP_USE_GNUTLS*/
+#else /*!HTTP_USE_GNUTLS && !HTTP_USE_NTBTLS*/
   {
-    (void)tls_priority;
+    (void)intended_hostname;
+    (void)flags;
   }
-#endif /*!HTTP_USE_GNUTLS*/
+#endif /*!HTTP_USE_GNUTLS && !HTTP_USE_NTBTLS*/
 
-  /* log_debug ("http.c:session_new: sess %p created\n", sess); */
+  if (opt_debug > 1)
+    log_debug ("http.c:session_new: sess %p created\n", sess);
   err = 0;
 
 #if USE_TLS
@@ -736,8 +855,9 @@ 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); */
+      if (opt_debug > 1)
+        log_debug ("http.c:session_ref: sess %p ref now %d\n",
+                   sess, sess->refcount);
     }
   return sess;
 }
@@ -753,6 +873,15 @@ http_session_set_log_cb (http_session_t sess,
 }
 
 
+/* 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;
+}
+
+
 
 \f
 /* Start a HTTP retrieval and on success store at R_HD a context
@@ -777,13 +906,16 @@ http_open (http_t *r_hd, http_req_t reqtype, const char *url,
   hd = xtrycalloc (1, sizeof *hd);
   if (!hd)
     return gpg_error_from_syserror ();
+  hd->magic = HTTP_CONTEXT_MAGIC;
   hd->req_type = reqtype;
   hd->flags = flags;
   hd->session = http_session_ref (session);
 
   err = parse_uri (&hd->uri, url, 0, !!(flags & HTTP_FLAG_FORCE_TLS));
   if (!err)
-    err = send_request (hd, httphost, auth, proxy, srvtag, headers);
+    err = send_request (hd, httphost, auth, proxy, srvtag,
+                        hd->session? hd->session->connect_timeout : 0,
+                        headers);
 
   if (err)
     {
@@ -803,15 +935,14 @@ http_open (http_t *r_hd, http_req_t reqtype, const char *url,
 
 /* 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.  */
+   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 flags, const char *srvtag, unsigned int timeout)
 {
   gpg_error_t err = 0;
   http_t hd;
   cookie_t cookie;
-  int hnf;
 
   *r_hd = NULL;
 
@@ -824,12 +955,17 @@ http_raw_connect (http_t *r_hd, const char *server, unsigned short port,
           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;
 
@@ -837,12 +973,9 @@ http_raw_connect (http_t *r_hd, const char *server, unsigned short port,
   {
     assuan_fd_t sock;
 
-    sock = connect_server (server, port, hd->flags, srvtag, &hnf);
-    if (sock == ASSUAN_INVALID_FD)
+    err = connect_server (server, port, hd->flags, srvtag, timeout, &sock);
+    if (err)
       {
-        err = gpg_err_make (default_errsource,
-                            (hnf? GPG_ERR_UNKNOWN_HOST
-                             : gpg_err_code_from_syserror ()));
         xfree (hd);
         return err;
       }
@@ -919,6 +1052,8 @@ 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;
@@ -933,6 +1068,7 @@ 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);
@@ -943,6 +1079,7 @@ http_wait_response (http_t hd)
   if (!cookie)
     return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
 
+  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.  */
@@ -952,7 +1089,7 @@ http_wait_response (http_t hd)
      is not required but some very old servers (e.g. the original pksd
      keyserver didn't worked without it.  */
   if ((hd->flags & HTTP_FLAG_SHUTDOWN))
-    shutdown (hd->sock->fd, 1);
+    shutdown (FD2INT (hd->sock->fd), 1);
   hd->in_data = 0;
 
   /* Create a new cookie and a stream for reading.  */
@@ -961,7 +1098,7 @@ http_wait_response (http_t hd)
     return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
   cookie->sock = my_socket_ref (hd->sock);
   cookie->session = http_session_ref (hd->session);
-  cookie->use_tls = hd->uri->use_tls;
+  cookie->use_tls = use_tls;
 
   hd->read_cookie = cookie;
   hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
@@ -1015,6 +1152,8 @@ http_close (http_t hd, int keep_read_stream)
   if (!hd)
     return;
 
+  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);
@@ -1028,6 +1167,7 @@ http_close (http_t hd, int keep_read_stream)
   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)
     {
@@ -1062,7 +1202,7 @@ http_get_status_code (http_t hd)
 /* Return information pertaining to TLS.  If TLS is not in use for HD,
    NULL is returned.  WHAT is used ask for specific information:
 
-     (NULL) := Only check whether TLS is is use.  Returns an
+     (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.
  */
@@ -1085,14 +1225,16 @@ parse_uri (parsed_uri_t *ret_uri, const char *uri,
 {
   gpg_err_code_t ec;
 
-  *ret_uri = xtrycalloc (1, sizeof **ret_uri + strlen (uri));
+  *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)
     {
-      xfree (*ret_uri);
+      http_release_parsed_uri (*ret_uri);
       *ret_uri = NULL;
     }
   return gpg_err_make (default_errsource, ec);
@@ -1121,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;
@@ -1151,6 +1298,7 @@ do_parse_uri (parsed_uri_t uri, int only_local_part,
   uri->opaque = 0;
   uri->v6lit = 0;
   uri->onion = 0;
+  uri->explicit_port = 0;
 
   /* A quick validity check. */
   if (strspn (p, VALID_URI_CHARS) != n)
@@ -1223,12 +1371,13 @@ do_parse_uri (parsed_uri_t uri, int only_local_part,
            {
              *p3++ = '\0';
              uri->port = atoi (p3);
+              uri->explicit_port = 1;
            }
 
          if ((n = remove_escapes (uri->host)) < 0)
            return GPG_ERR_BAD_URI;
          if (n != strlen (uri->host))
-           return GPG_ERR_BAD_URI;     /* Hostname incudes a Nul. */
+           return GPG_ERR_BAD_URI;     /* Hostname includes a Nul. */
          p = p2 ? p2 : NULL;
        }
       else if (uri->is_http)
@@ -1516,7 +1665,8 @@ is_hostname_port (const char *string)
  */
 static gpg_error_t
 send_request (http_t hd, const char *httphost, const char *auth,
-             const char *proxy, const char *srvtag, strlist_t headers)
+             const char *proxy, const char *srvtag, unsigned int timeout,
+              strlist_t headers)
 {
   gpg_error_t err;
   const char *server;
@@ -1525,8 +1675,10 @@ send_request (http_t hd, const char *httphost, const char *auth,
   const char *http_proxy = NULL;
   char *proxy_authstr = NULL;
   char *authstr = NULL;
-  int sock;
-  int hnf;
+  assuan_fd_t sock;
+#ifdef USE_TLS
+  int have_http_proxy = 0;
+#endif
 
   if (hd->uri->use_tls && !hd->session)
     {
@@ -1550,6 +1702,10 @@ send_request (http_t hd, const char *httphost, const char *auth,
           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";
@@ -1596,7 +1752,6 @@ send_request (http_t hd, const char *httphost, const char *auth,
             && *http_proxy ))
     {
       parsed_uri_t uri;
-      int save_errno;
 
       if (proxy)
        http_proxy = proxy;
@@ -1614,9 +1769,12 @@ send_request (http_t hd, const char *httphost, const char *auth,
 
       if (err)
         ;
-      else if (!strcmp (uri->scheme, "http") || !strcmp (uri->scheme, "socks4"))
-        ;
-      else if (!strcmp (uri->scheme, "socks5h"))
+#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);
@@ -1643,25 +1801,20 @@ send_request (http_t hd, const char *httphost, const char *auth,
             }
         }
 
-      sock = connect_server (*uri->host ? uri->host : "localhost",
-                             uri->port ? uri->port : 80,
-                             hd->flags, srvtag, &hnf);
-      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);
-      if (sock == ASSUAN_INVALID_FD)
-        gpg_err_set_errno (save_errno);
     }
   else
     {
-      sock = connect_server (server, port, hd->flags, srvtag, &hnf);
+      err = connect_server (server, port, hd->flags, srvtag, timeout, &sock);
     }
 
-  if (sock == ASSUAN_INVALID_FD)
+  if (err)
     {
       xfree (proxy_authstr);
-      return gpg_err_make (default_errsource,
-                           (hnf? GPG_ERR_UNKNOWN_HOST
-                               : gpg_err_code_from_syserror ()));
+      return err;
     }
   hd->sock = my_socket_new (sock);
   if (!hd->sock)
@@ -1670,13 +1823,155 @@ send_request (http_t hd, const char *httphost, const char *auth,
       return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
     }
 
+#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)
     {
+      estream_t in, out;
+
       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)
+        {
+          err = gpg_error_from_syserror ();
+          xfree (proxy_authstr);
+          return err;
+        }
+
+#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)
+        {
+          err = gpg_error_from_syserror ();
+          es_fclose (in);
+          xfree (proxy_authstr);
+          return err;
+        }
+
+      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 = ntbtls_set_verify_cb (hd->session->tls_session,
+                                      my_ntbtls_verify_cb, hd);
+          if (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)
@@ -1690,10 +1985,33 @@ send_request (http_t hd, const char *httphost, const char *auth,
         }
 
       hd->session->verify.done = 0;
-      if (tls_callback)
+
+      /* 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);
-      else
+
+      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",
@@ -1701,6 +2019,7 @@ send_request (http_t hd, const char *httphost, const char *auth,
           xfree (proxy_authstr);
           return err;
         }
+
     }
 #elif HTTP_USE_GNUTLS
   if (hd->uri->use_tls)
@@ -1714,6 +2033,7 @@ send_request (http_t hd, const char *httphost, const char *auth,
       gnutls_transport_set_push_function (hd->session->tls_session,
                                           my_gnutls_write);
 
+    handshake_again:
       do
         {
           rc = gnutls_handshake (hd->session->tls_session);
@@ -1729,10 +2049,15 @@ send_request (http_t hd, const char *httphost, const char *auth,
 
               alertno = gnutls_alert_get (hd->session->tls_session);
               alertstr = gnutls_alert_get_name (alertno);
-              log_info ("TLS handshake failed: %s (alert %d)\n",
+              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));
@@ -1809,7 +2134,7 @@ send_request (http_t hd, const char *httphost, const char *auth,
     {
       char portstr[35];
 
-      if (port == 80)
+      if (port == (hd->uri->use_tls? 443 : 80))
         *portstr = 0;
       else
         snprintf (portstr, sizeof portstr, ":%u", port);
@@ -1833,7 +2158,8 @@ send_request (http_t hd, const char *httphost, const char *auth,
       return err;
     }
 
-  /* log_debug ("request:\n%s\nEND request\n", request); */
+  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. */
@@ -1868,6 +2194,8 @@ send_request (http_t hd, const char *httphost, const char *auth,
     {
       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)))
             {
@@ -1986,7 +2314,7 @@ store_header (http_t hd, char *line)
   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_ERR_PROTOCOL_VIOLATION;
       n += strlen (hd->headers->value);
@@ -2014,11 +2342,10 @@ store_header (http_t hd, char *line)
   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_err_code_from_syserror ();
-      strcpy (stpcpy (stpcpy (p, h->value), ","), value);
       xfree (h->value);
       h->value = p;
       return 0;
@@ -2118,9 +2445,8 @@ parse_response (http_t hd)
       if (!len)
        return GPG_ERR_EOF;
 
-      if ((hd->flags & HTTP_FLAG_LOG_RESP))
-        log_info ("RESP: '%.*s'\n",
-                  (int)strlen(line)-(*line&&line[1]?2:0),line);
+      if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
+        log_debug_with_string (line, "http.c:response:\n");
     }
   while (!*line);
 
@@ -2164,8 +2490,8 @@ parse_response (http_t hd)
       /* 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 (*line)
         {
@@ -2267,23 +2593,240 @@ start_server ()
 }
 #endif
 
-/* Actually connect to a server.  Returns the file descriptor 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, int *r_host_not_found)
+                unsigned int flags, const char *srvtag, unsigned int timeout,
+                assuan_fd_t *r_sock)
 {
   gpg_error_t err;
   assuan_fd_t sock = ASSUAN_INVALID_FD;
-  int srvcount = 0;
+  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;
-  int ret;
 
-  *r_host_not_found = 0;
+  *r_sock = ASSUAN_INVALID_FD;
+
 #if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
   init_sockets ();
 #endif /*Windows*/
@@ -2293,54 +2836,42 @@ connect_server (const char *server, unsigned short port,
     {
 #ifdef ASSUAN_SOCK_TOR
 
+      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)
         {
-          if (errno == EHOSTUNREACH)
-            *r_host_not_found = 1;
-          log_error ("can't connect to '%s': %s\n", server, strerror (errno));
+          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;
         }
-      else
-        notify_netactivity ();
-      return sock;
+
+      notify_netactivity ();
+      *r_sock = sock;
+      return 0;
 
 #else /*!ASSUAN_SOCK_TOR*/
 
-      gpg_err_set_errno (ENETUNREACH);
-      return -1; /* Out of core.  */
+      err = gpg_err_make (default_errsource, GPG_ERR_ENETUNREACH);
+      return ASSUAN_INVALID_FD;
 
 #endif /*!HASSUAN_SOCK_TOR*/
     }
 
-#ifdef USE_DNS_SRV
   /* Do the SRV thing */
   if (srvtag)
     {
-      /* We're using SRV, so append the tags. */
-      if (1 + strlen (srvtag) + 6 + strlen (server) + 1
-          <= DIMof (struct srventry, target))
-       {
-         char *srvname = xtrymalloc (DIMof (struct srventry, target));
-
-          if (!srvname) /* Out of core */
-            {
-              serverlist = NULL;
-              srvcount = 0;
-            }
-          else
-            {
-              stpcpy (stpcpy (stpcpy (stpcpy (srvname,"_"), srvtag),
-                              "._tcp."), server);
-              srvcount = getsrv (srvname, &serverlist);
-              xfree (srvname);
-            }
-       }
+      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
-  (void)flags;
-  (void)srvtag;
-#endif /*USE_DNS_SRV*/
 
   if (!serverlist)
     {
@@ -2348,7 +2879,8 @@ 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, DIMof (struct srventry, target));
       serverlist->target[DIMof (struct srventry, target)-1] = '\0';
@@ -2360,12 +2892,16 @@ connect_server (const char *server, unsigned short port,
     {
       dns_addrinfo_t aibuf, ai;
 
+      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;
@@ -2379,21 +2915,24 @@ connect_server (const char *server, unsigned short port,
 
           if (sock != ASSUAN_INVALID_FD)
             assuan_sock_close (sock);
-          sock = assuan_sock_new (ai->family, ai->socktype, ai->protocol);
+          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));
+              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 ASSUAN_INVALID_FD;
+              return err;
             }
 
           anyhostaddr = 1;
-          ret = assuan_sock_connect (sock, ai->addr, ai->addrlen);
-          if (ret)
-            last_errno = errno;
+          err = connect_with_timeout (sock, (struct sockaddr *)ai->addr,
+                                      ai->addrlen, timeout);
+          if (err)
+            {
+              last_err = err;
+            }
           else
             {
               connected = 1;
@@ -2420,22 +2959,58 @@ connect_server (const char *server, unsigned short port,
                    server, (int)WSAGetLastError());
 #else
         log_error ("can't connect to '%s': %s\n",
-                   server, strerror (last_errno));
+                   server, gpg_strerror (last_err));
 #endif
         }
-      if (!hostfound || (hostfound && !anyhostaddr))
-        *r_host_not_found = 1;
+      err = last_err? last_err : gpg_err_make (default_errsource,
+                                               GPG_ERR_UNKNOWN_HOST);
       if (sock != ASSUAN_INVALID_FD)
        assuan_sock_close (sock);
-      gpg_err_set_errno (last_errno);
-      return ASSUAN_INVALID_FD;
+      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 (int sock, const char *data, size_t length)
+write_server (assuan_fd_t sock, const char *data, size_t length)
 {
   int nleft;
   int nwritten;
@@ -2447,7 +3022,7 @@ write_server (int sock, const char *data, size_t length)
 # if defined(USE_NPTH)
       npth_unprotect ();
 # endif
-      nwritten = send (sock, data, nleft, 0);
+      nwritten = send (FD2INT (sock), data, nleft, 0);
 # if defined(USE_NPTH)
       npth_protect ();
 # endif
@@ -2503,7 +3078,18 @@ cookie_read (void *cookie, void *buffer, size_t size)
         size = c->content_length;
     }
 
-#ifdef HTTP_USE_GNUTLS
+#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:
@@ -2538,29 +3124,7 @@ cookie_read (void *cookie, void *buffer, size_t size)
   else
 #endif /*HTTP_USE_GNUTLS*/
     {
-      do
-        {
-#ifdef HAVE_W32_SYSTEM
-          /* Under Windows we need to use recv for a socket.  */
-# if defined(USE_NPTH)
-          npth_unprotect ();
-# endif
-          nread = recv (c->sock->fd, buffer, size, 0);
-# if defined(USE_NPTH)
-          npth_protect ();
-# endif
-
-#else /*!HAVE_W32_SYSTEM*/
-
-# ifdef USE_NPTH
-          nread = npth_read (c->sock->fd, buffer, size);
-# else
-          nread = read (c->sock->fd, buffer, size);
-# endif
-
-#endif /*!HAVE_W32_SYSTEM*/
-        }
-      while (nread == -1 && errno == EINTR);
+      nread = read_server (c->sock->fd, buffer, size);
     }
 
   if (c->content_length_valid && nread > 0)
@@ -2582,7 +3146,21 @@ cookie_write (void *cookie, const void *buffer_arg, size_t size)
   cookie_t c = cookie;
   int nwritten = 0;
 
-#ifdef HTTP_USE_GNUTLS
+#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;
@@ -2628,6 +3206,34 @@ cookie_write (void *cookie, const void *buffer_arg, size_t size)
 }
 
 
+#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;
+    }
+  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
@@ -2661,7 +3267,15 @@ cookie_close (void *cookie)
   if (!c)
     return 0;
 
-#ifdef HTTP_USE_GNUTLS
+#if HTTP_USE_NTBTLS
+  if (c->use_tls && c->session && c->session->tls_session)
+    {
+      /* 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
@@ -2683,11 +3297,8 @@ cookie_close (void *cookie)
 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";
+#if HTTP_USE_GNUTLS
+  static const char errprefix[] = "TLS verification of peer failed";
   int rc;
   unsigned int status;
   const char *hostname;