common: Minor change of hex2str to allow for embedded nul.
[gnupg.git] / common / http.c
index 8a1ad67..c2cac16 100644 (file)
@@ -2,6 +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
  *
  * This file is part of GnuPG.
  *
@@ -39,7 +40,7 @@
   - fixme: list other requirements.
 
 
-  - With HTTP_USE_GNUTLS or HTTP_USE_POLARSSL support for https is
+  - 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
 # include <npth.h>
 #endif
 
-#if defined (HTTP_USE_GNUTLS) && defined (HTTP_USE_POLARSSL)
-# error Both, HTTP_USE_GNUTLS and HTTP_USE_POLARSSL, are defined.
+#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>
 # include <gnutls/x509.h>
 #endif /*HTTP_USE_GNUTLS*/
-#ifdef HTTP_USE_POLARSSL
-# error Support for PolarSSL has not yet been added
-#endif
 
 
 #include "util.h"
@@ -156,8 +156,15 @@ 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,
@@ -226,18 +233,22 @@ struct http_session_s
   int refcount;    /* Number of references to this object.  */
 #ifdef HTTP_USE_GNUTLS
   gnutls_certificate_credentials_t certcred;
-  gnutls_session_t tls_session;
+#endif /*HTTP_USE_GNUTLS*/
+#ifdef USE_TLS
+  tls_session_t tls_session;
   struct {
     int done;      /* Verifciation has been done.  */
-    int rc;        /* GnuTLS verification return code.  */
+    int rc;        /* TLS verification return code.  */
     unsigned int status; /* Verification status.  */
   } verify;
   char *servername; /* Malloced server name.  */
-#endif /*HTTP_USE_GNUTLS*/
+#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. */
 struct header_s
 {
@@ -520,7 +531,8 @@ session_unref (int lnr, http_session_t sess)
   if (sess->refcount)
     return;
 
-#ifdef HTTP_USE_GNUTLS
+#ifdef USE_TLS
+# ifdef HTTP_USE_GNUTLS
   if (sess->tls_session)
     {
       my_socket_t sock = gnutls_transport_get_ptr (sess->tls_session);
@@ -529,8 +541,9 @@ session_unref (int lnr, http_session_t sess)
     }
   if (sess->certcred)
     gnutls_certificate_free_credentials (sess->certcred);
+# endif /*HTTP_USE_GNUTLS*/
   xfree (sess->servername);
-#endif /*HTTP_USE_GNUTLS*/
+#endif /*USE_TLS*/
 
   xfree (sess);
 }
@@ -558,7 +571,18 @@ http_session_new (http_session_t *r_session, const char *tls_priority)
     return gpg_error_from_syserror ();
   sess->refcount = 1;
 
-#ifdef HTTP_USE_GNUTLS
+#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;
@@ -614,17 +638,18 @@ http_session_new (http_session_t *r_session, const char *tls_priority)
         goto leave;
       }
   }
-
 #else /*!HTTP_USE_GNUTLS*/
-  (void)tls_priority;
+  {
+    (void)tls_priority;
+  }
 #endif /*!HTTP_USE_GNUTLS*/
 
   /* log_debug ("http.c:session_new: sess %p created\n", sess); */
   err = 0;
 
-#ifdef HTTP_USE_GNUTLS
+#if USE_TLS
  leave:
-#endif /*HTTP_USE_GNUTLS*/
+#endif /*USE_TLS*/
   if (err)
     http_session_unref (sess);
   else
@@ -634,16 +659,33 @@ http_session_new (http_session_t *r_session, const char *tls_priority)
 }
 
 
-/* Increment the reference count for session SESS.  */
+/* Increment the reference count for session SESS.  Passing NULL for
+   SESS is allowed. */
 http_session_t
 http_session_ref (http_session_t sess)
 {
-  sess->refcount++;
-  /* log_debug ("http.c:session_ref: sess %p ref now %d\n", sess, sess->refcount); */
+  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
@@ -976,9 +1018,10 @@ parse_uri (parsed_uri_t *ret_uri, const char *uri,
 
 /*
  * 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.
+ * 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,
@@ -1048,7 +1091,7 @@ do_parse_uri (parsed_uri_t uri, int only_local_part,
           uri->port = 11371;
           uri->is_http = 1;
         }
-#ifdef HTTP_USE_GNUTLS
+#ifdef USE_TLS
       else if (!strcmp (uri->scheme, "https") || !strcmp (uri->scheme,"hkps")
                || (force_tls && (!strcmp (uri->scheme, "http")
                                  || !strcmp (uri->scheme,"hkp"))))
@@ -1057,7 +1100,7 @@ do_parse_uri (parsed_uri_t uri, int only_local_part,
           uri->is_http = 1;
           uri->use_tls = 1;
         }
-#endif
+#endif /*USE_TLS*/
       else if (!no_scheme_check)
        return GPG_ERR_INV_URI; /* Unsupported scheme */
 
@@ -1351,6 +1394,33 @@ 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
@@ -1374,22 +1444,24 @@ send_request (http_t hd, const char *httphost, const char *auth,
       log_error ("TLS requested but no session object provided\n");
       return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
     }
-#ifdef HTTP_USE_GNUTLS
+#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 /*HTTP_USE_GNUTLS*/
+#endif /*USE_TLS*/
 
   server = *hd->uri->host ? hd->uri->host : "localhost";
   port = hd->uri->port ? hd->uri->port : 80;
 
   /* Try to use SNI.  */
-#ifdef HTTP_USE_GNUTLS
+#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);
@@ -1399,13 +1471,24 @@ send_request (http_t hd, const char *httphost, const char *auth,
           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,
-                                   server, strlen (server));
+                                   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 /*HTTP_USE_GNUTLS*/
+#endif /*USE_TLS*/
 
   if ( (proxy && *proxy)
        || ( (hd->flags & HTTP_FLAG_TRY_PROXY)
@@ -1418,8 +1501,26 @@ send_request (http_t hd, const char *httphost, const char *auth,
       if (proxy)
        http_proxy = proxy;
 
-      err = parse_uri (&uri, http_proxy, 0,
-                       !!(hd->flags & HTTP_FLAG_FORCE_TLS));
+      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",
@@ -1471,7 +1572,37 @@ send_request (http_t hd, const char *httphost, 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;
@@ -1492,7 +1623,21 @@ send_request (http_t hd, const char *httphost, const char *auth,
       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);
         }
@@ -1551,7 +1696,7 @@ send_request (http_t hd, const char *httphost, const char *auth,
 
   if (http_proxy && *http_proxy)
     {
-      request = es_asprintf
+      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" :
@@ -1571,7 +1716,7 @@ send_request (http_t hd, const char *httphost, 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" :
@@ -2033,6 +2178,7 @@ 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;
@@ -2139,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
@@ -2192,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;
@@ -2208,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);
@@ -2238,14 +2392,20 @@ write_server (int sock, const char *data, size_t length)
   nleft = length;
   while (nleft > 0)
     {
-#if defined(HAVE_W32_SYSTEM) && !defined(USE_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 || USE_NPTH*/
+#else /*!HAVE_W32_SYSTEM*/
 # ifdef USE_NPTH
       nwritten = npth_write (sock, data, nleft);
 # else
@@ -2267,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 || USE_NPTH*/
+#endif /*!HAVE_W32_SYSTEM*/
       nleft -= nwritten;
       data += nwritten;
     }
@@ -2322,14 +2482,25 @@ cookie_read (void *cookie, void *buffer, size_t size)
     {
       do
         {
-#ifdef USE_NPTH
-          nread = npth_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);
     }
@@ -2404,9 +2575,22 @@ cookie_write (void *cookie, const void *buffer_arg, size_t size)
 static void
 send_gnutls_bye (void *opaque)
 {
-  gnutls_session_t tls_session = opaque;
+  tls_session_t tls_session = opaque;
+  int ret;
 
-  gnutls_bye (tls_session, GNUTLS_SHUT_RDWR);
+ 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*/
 
@@ -2441,7 +2625,10 @@ cookie_close (void *cookie)
 gpg_error_t
 http_verify_server_credentials (http_session_t sess)
 {
-#ifdef HTTP_USE_GNUTLS
+#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;
   unsigned int status;
@@ -2472,6 +2659,19 @@ http_verify_server_credentials (http_session_t sess)
   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*/
+
       sess->verify.status = status;
       if (!err)
         err = gpg_error (GPG_ERR_GENERAL);
@@ -2497,24 +2697,6 @@ http_verify_server_credentials (http_session_t sess)
         return err;
     }
 
-  /* log_debug ("Server sent %u certs\n", certlistlen); */
-  /* { */
-  /*   int i; */
-  /*   char fname[50]; */
-  /*   FILE *fp; */
-
-  /*   for (i=0; i < certlistlen; i++) */
-  /*     { */
-  /*       snprintf (fname, sizeof fname, "xc_%d.der", i); */
-  /*       fp = fopen (fname, "wb"); */
-  /*       if (!fp) */
-  /*         log_fatal ("Failed to create '%s'\n", fname); */
-  /*       if (fwrite (certlist[i].data, certlist[i].size, 1, fp) != 1) */
-  /*         log_fatal ("Error writing to '%s'\n", fname); */
-  /*       fclose (fp); */
-  /*     } */
-  /* } */
-
   rc = gnutls_x509_crt_init (&cert);
   if (rc < 0)
     {
@@ -2536,17 +2718,48 @@ http_verify_server_credentials (http_session_t sess)
   if (!gnutls_x509_crt_check_hostname (cert, hostname))
     {
       log_error ("%s: %s\n", errprefix, "hostname does not match");
-      log_info ("(expected '%s')\n", hostname);
       if (!err)
         err = gpg_error (GPG_ERR_GENERAL);
     }
 
   gnutls_x509_crt_deinit (cert);
+
   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
 }
+
+/* Return the first query variable with the specified key.  If there
+   is no such variable, return NULL.  */
+struct uri_tuple_s *
+uri_query_lookup (parsed_uri_t uri, const char *key)
+{
+  struct uri_tuple_s *t;
+
+  for (t = uri->query; t; t = t->next)
+    if (strcmp (t->name, key) == 0)
+      return t;
+
+  return NULL;
+}