libdns: Silence -Wstrict-prototypes on some function ptrs.
[gnupg.git] / dirmngr / http.c
index f0fcd0d..75701ec 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.
  *
@@ -27,7 +27,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 /* Simple HTTP client implementation.  We try to keep the code as
                         "01234567890@"                 \
                         "!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
 
-/* A long counter type.  */
-#ifdef HAVE_STRTOULL
-typedef unsigned long long longcounter_t;
-# define counter_strtoul(a) strtoull ((a), NULL, 10)
-#else
-typedef unsigned long longcounter_t;
-# define counter_strtoul(a) strtoul ((a), NULL, 10)
-#endif
-
 #if HTTP_USE_NTBTLS
 typedef ntbtls_t         tls_session_t;
 # define USE_TLS 1
@@ -206,7 +197,7 @@ struct cookie_s
 
   /* The remaining content length and a flag telling whether to use
      the content length.  */
-  longcounter_t content_length;
+  uint64_t content_length;
   unsigned int content_length_valid:1;
 };
 typedef struct cookie_s *cookie_t;
@@ -264,12 +255,21 @@ struct http_context_s
 };
 
 
+/* Two flags to enable verbose and debug mode.  Although currently not
+ * set-able a value > 1 for OPT_DEBUG enables debugging of the session
+ * reference counting.  */
+static int opt_verbose;
+static int opt_debug;
+
 /* The global callback for the verification function.  */
 static gpg_error_t (*tls_callback) (http_t, http_session_t, int);
 
 /* The list of files with trusted CA certificates.  */
 static strlist_t tls_ca_certlist;
 
+/* The global callback for net activity.  */
+static void (*netactivity_cb)(void);
+
 
 \f
 #if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
@@ -336,9 +336,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, so->fd);
   return so;
 }
 #define my_socket_new(a) _my_socket_new (__LINE__, (a))
@@ -348,9 +348,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, so->fd, so->refcount);
   return so;
 }
 #define my_socket_ref(a) _my_socket_ref (__LINE__,(a))
@@ -366,9 +366,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, so->fd, so->refcount);
+
       if (!so->refcount)
         {
           if (preclose)
@@ -475,6 +476,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.  */
@@ -501,6 +511,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;
@@ -508,6 +523,25 @@ http_register_tls_ca (const char *fname)
 }
 
 
+/* Register a callback which is called every time the HTTP mode has
+ * made a successful connection to some server.  */
+void
+http_register_netactivity_cb (void (*cb)(void))
+{
+  netactivity_cb = cb;
+}
+
+
+/* Call the netactivity callback if any.  */
+static void
+notify_netactivity (void)
+{
+  if (netactivity_cb)
+    netactivity_cb ();
+}
+
+
+
 #ifdef USE_TLS
 /* Free the TLS session associated with SESS, if any.  */
 static void
@@ -515,7 +549,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);
@@ -538,9 +578,9 @@ session_unref (int lnr, http_session_t sess)
     return;
 
   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;
 
@@ -583,6 +623,8 @@ http_session_new (http_session_t *r_session, const char *tls_priority,
   {
     (void)tls_priority;
 
+    /* ntbtls_set_debug (99, NULL, NULL); */
+
     err = ntbtls_new (&sess->tls_session, NTBTLS_CLIENT);
     if (err)
       {
@@ -595,6 +637,8 @@ http_session_new (http_session_t *r_session, const char *tls_priority,
     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)
@@ -605,13 +649,14 @@ http_session_new (http_session_t *r_session, const char *tls_priority,
         goto leave;
       }
 
+    is_hkps_pool = (intended_hostname
+                    && !ascii_strcasecmp (intended_hostname,
+                                          "hkps.pool.sks-keyservers.net"));
+
     /* 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);
@@ -644,10 +689,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;
@@ -700,7 +747,8 @@ http_session_new (http_session_t *r_session, const char *tls_priority,
   }
 #endif /*!HTTP_USE_GNUTLS*/
 
-  /* 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
@@ -723,8 +771,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;
 }
@@ -744,7 +793,7 @@ http_session_set_log_cb (http_session_t sess,
 \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
+   If HTTPHOST is not NULL it is used for the Host header instead of a
    Host header derived from the URL. */
 gpg_error_t
 http_open (http_t *r_hd, http_req_t reqtype, const char *url,
@@ -906,6 +955,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;
@@ -937,7 +988,7 @@ http_wait_response (http_t hd)
 
   /* Shutdown one end of the socket is desired.  As per HTTP/1.0 this
      is not required but some very old servers (e.g. the original pksd
-     key server didn't worked without it.  */
+     keyserver didn't worked without it.  */
   if ((hd->flags & HTTP_FLAG_SHUTDOWN))
     shutdown (hd->sock->fd, 1);
   hd->in_data = 0;
@@ -1138,6 +1189,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)
@@ -1210,6 +1262,7 @@ 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)
@@ -1662,8 +1715,36 @@ send_request (http_t hd, const char *httphost, const char *auth,
 #if HTTP_USE_NTBTLS
   if (hd->uri->use_tls)
     {
+      estream_t in, out;
+
       my_socket_ref (hd->sock);
 
+      in = es_fdopen_nc (hd->sock->fd, "rb");
+      if (!in)
+        {
+          err = gpg_error_from_syserror ();
+          xfree (proxy_authstr);
+          return err;
+        }
+
+      out = es_fdopen_nc (hd->sock->fd, "wb");
+      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;
+        }
+
       while ((err = ntbtls_handshake (hd->session->tls_session)))
         {
           switch (err)
@@ -1820,7 +1901,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. */
@@ -1855,6 +1937,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)))
             {
@@ -2106,8 +2190,7 @@ parse_response (http_t hd)
        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);
+        log_debug_with_string (line, "http.c:response:\n");
     }
   while (!*line);
 
@@ -2152,7 +2235,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 ("http.c:RESP: '%.*s'\n",
                   (int)strlen(line)-(*line&&line[1]?2:0),line);
       if (*line)
         {
@@ -2170,7 +2253,7 @@ parse_response (http_t hd)
       if (s)
         {
           cookie->content_length_valid = 1;
-          cookie->content_length = counter_strtoul (s);
+          cookie->content_length = string_to_u64 (s);
         }
     }
 
@@ -2254,6 +2337,68 @@ start_server ()
 }
 #endif
 
+
+
+/* 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 fucntion in
+ * Libassuan.  */
+static int
+use_socks (struct sockaddr *addr)
+{
+  int mode;
+
+  if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
+    return 0;  /* Not in Tor mode.  */
+  else if (addr->sa_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->sa_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 *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->sa_family;
+
+  return assuan_sock_new (domain, type, proto);
+}
+
+
 /* Actually connect to a server.  Returns the file descriptor or -1 on
    error.  ERRNO is set on error. */
 static assuan_fd_t
@@ -2262,7 +2407,7 @@ connect_server (const char *server, unsigned short port,
 {
   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;
@@ -2280,6 +2425,9 @@ 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)
@@ -2288,6 +2436,8 @@ connect_server (const char *server, unsigned short port,
             *r_host_not_found = 1;
           log_error ("can't connect to '%s': %s\n", server, strerror (errno));
         }
+      else
+        notify_netactivity ();
       return sock;
 
 #else /*!ASSUAN_SOCK_TOR*/
@@ -2298,34 +2448,15 @@ connect_server (const char *server, unsigned short port,
 #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.  */
     }
-#else
-  (void)flags;
-  (void)srvtag;
-#endif /*USE_DNS_SRV*/
 
   if (!serverlist)
     {
@@ -2345,6 +2476,9 @@ 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)
@@ -2364,7 +2498,7 @@ 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;
@@ -2380,7 +2514,10 @@ connect_server (const char *server, unsigned short port,
           if (ret)
             last_errno = errno;
           else
-            connected = 1;
+            {
+              connected = 1;
+              notify_netactivity ();
+            }
         }
       free_dns_addrinfo (aibuf);
     }
@@ -2485,7 +2622,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/%u\n", nread, size);
+    }
+  else
+#elif HTTP_USE_GNUTLS
   if (c->use_tls && c->session && c->session->tls_session)
     {
     again:
@@ -2564,7 +2712,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/%u\n", nwritten, size);
+    }
+  else
+#elif HTTP_USE_GNUTLS
   if (c->use_tls && c->session && c->session->tls_session)
     {
       int nleft = size;
@@ -2643,7 +2805,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