build: Require at least Libassuan 2.4.1.
[gnupg.git] / dirmngr / http.c
index edd8a6d..74b6911 100644 (file)
 # include <gnutls/x509.h>
 #endif /*HTTP_USE_GNUTLS*/
 
+#include <assuan.h>  /* We need the socket wrapper.  */
 
 #include "util.h"
 #include "i18n.h"
+#include "dns-stuff.h"
 #include "http.h"
-#ifdef USE_DNS_SRV
-# include "srv.h"
-#else /*!USE_DNS_SRV*/
-  /* If we are not compiling with SRV record support we provide stub
-     data structures. */
-# ifndef MAXDNAME
-#  define MAXDNAME 1025
-# endif
-struct srventry
-{
-  unsigned short priority;
-  unsigned short weight;
-  unsigned short port;
-  int run_count;
-  char target[MAXDNAME];
-};
-#endif/*!USE_DNS_SRV*/
 
 
 #ifdef USE_NPTH
 # define my_select(a,b,c,d,e)  npth_select ((a), (b), (c), (d), (e))
-# define my_connect(a,b,c)     npth_connect ((a), (b), (c))
 # define my_accept(a,b,c)      npth_accept ((a), (b), (c))
 #else
 # define my_select(a,b,c,d,e)  select ((a), (b), (c), (d), (e))
-# define my_connect(a,b,c)     connect ((a), (b), (c))
 # define my_accept(a,b,c)      accept ((a), (b), (c))
 #endif
 
@@ -181,21 +164,22 @@ static gpg_error_t send_request (http_t hd, const char *httphost,
 static char *build_rel_path (parsed_uri_t uri);
 static gpg_error_t parse_response (http_t hd);
 
-static int connect_server (const char *server, unsigned short port,
-                           unsigned int flags, const char *srvtag,
-                           int *r_host_not_found);
+static assuan_fd_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);
 
-static ssize_t cookie_read (void *cookie, void *buffer, size_t size);
-static ssize_t cookie_write (void *cookie, const void *buffer, size_t size);
+static gpgrt_ssize_t cookie_read (void *cookie, void *buffer, size_t size);
+static gpgrt_ssize_t cookie_write (void *cookie,
+                                   const void *buffer, size_t size);
 static int cookie_close (void *cookie);
 
 
 /* A socket object used to a allow ref counting of sockets.  */
 struct my_socket_s
 {
-  int fd;       /* The actual socket - shall never be -1.  */
-  int refcount; /* Number of references to this socket.  */
+  assuan_fd_t fd; /* The actual socket - shall never be ASSUAN_INVALID_FD.  */
+  int refcount;   /* Number of references to this socket.  */
 };
 typedef struct my_socket_s *my_socket_t;
 
@@ -280,7 +264,7 @@ struct http_context_s
 };
 
 
-/* The global callback for the verification fucntion.  */
+/* 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.  */
@@ -338,7 +322,7 @@ init_sockets (void)
 /* Create a new socket object.  Returns NULL and closes FD if not
    enough memory is available.  */
 static my_socket_t
-_my_socket_new (int lnr, int fd)
+_my_socket_new (int lnr, assuan_fd_t fd)
 {
   my_socket_t so;
 
@@ -346,7 +330,7 @@ _my_socket_new (int lnr, int fd)
   if (!so)
     {
       int save_errno = errno;
-      sock_close (fd);
+      assuan_sock_close (fd);
       gpg_err_set_errno (save_errno);
       return NULL;
     }
@@ -389,7 +373,7 @@ _my_socket_unref (int lnr, my_socket_t so,
         {
           if (preclose)
             preclose (preclosearg);
-          sock_close (so->fd);
+          assuan_sock_close (so->fd);
           xfree (so);
         }
     }
@@ -397,20 +381,28 @@ _my_socket_unref (int lnr, my_socket_t so,
 #define my_socket_unref(a,b,c) _my_socket_unref (__LINE__,(a),(b),(c))
 
 
-#if defined (USE_NPTH) && defined(HTTP_USE_GNUTLS)
+#ifdef HTTP_USE_GNUTLS
 static ssize_t
-my_npth_read (gnutls_transport_ptr_t ptr, void *buffer, size_t size)
+my_gnutls_read (gnutls_transport_ptr_t ptr, void *buffer, size_t size)
 {
   my_socket_t sock = ptr;
+#if USE_NPTH
   return npth_read (sock->fd, buffer, size);
+#else
+  return read (sock->fd, buffer, size);
+#endif
 }
 static ssize_t
-my_npth_write (gnutls_transport_ptr_t ptr, const void *buffer, size_t size)
+my_gnutls_write (gnutls_transport_ptr_t ptr, const void *buffer, size_t size)
 {
   my_socket_t sock = ptr;
+#if USE_NPTH
   return npth_write (sock->fd, buffer, size);
+#else
+  return write (sock->fd, buffer, size);
+#endif
 }
-#endif /*USE_NPTH && HTTP_USE_GNUTLS*/
+#endif /*HTTP_USE_GNUTLS*/
 
 
 
@@ -516,6 +508,27 @@ http_register_tls_ca (const char *fname)
 }
 
 
+#ifdef USE_TLS
+/* Free the TLS session associated with SESS, if any.  */
+static void
+close_tls_session (http_session_t sess)
+{
+  if (sess->tls_session)
+    {
+# ifdef HTTP_USE_GNUTLS
+      my_socket_t sock = gnutls_transport_get_ptr (sess->tls_session);
+      my_socket_unref (sock, NULL, NULL);
+      gnutls_deinit (sess->tls_session);
+      if (sess->certcred)
+        gnutls_certificate_free_credentials (sess->certcred);
+# endif /*HTTP_USE_GNUTLS*/
+      xfree (sess->servername);
+      sess->tls_session = NULL;
+    }
+}
+#endif /*USE_TLS*/
+
+
 /* Release a session.  Take care not to release it while it is being
    used by a http context object.  */
 static void
@@ -532,17 +545,7 @@ session_unref (int lnr, http_session_t sess)
     return;
 
 #ifdef USE_TLS
-# ifdef HTTP_USE_GNUTLS
-  if (sess->tls_session)
-    {
-      my_socket_t sock = gnutls_transport_get_ptr (sess->tls_session);
-      my_socket_unref (sock, NULL, NULL);
-      gnutls_deinit (sess->tls_session);
-    }
-  if (sess->certcred)
-    gnutls_certificate_free_credentials (sess->certcred);
-# endif /*HTTP_USE_GNUTLS*/
-  xfree (sess->servername);
+  close_tls_session (sess);
 #endif /*USE_TLS*/
 
   xfree (sess);
@@ -740,7 +743,6 @@ http_raw_connect (http_t *r_hd, const char *server, unsigned short port,
                   unsigned int flags, const char *srvtag)
 {
   gpg_error_t err = 0;
-  int sock;
   http_t hd;
   cookie_t cookie;
   int hnf;
@@ -749,8 +751,13 @@ http_raw_connect (http_t *r_hd, const char *server, unsigned short port,
 
   if ((flags & HTTP_FLAG_FORCE_TOR))
     {
-      log_error ("TOR support is not yet available\n");
-      return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
+      int mode;
+
+      if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
+        {
+          log_error ("Tor support is not available\n");
+          return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
+        }
     }
 
   /* Create the handle. */
@@ -761,22 +768,26 @@ http_raw_connect (http_t *r_hd, const char *server, unsigned short port,
   hd->flags = flags;
 
   /* Connect.  */
-  sock = connect_server (server, port, hd->flags, srvtag, &hnf);
-  if (sock == -1)
-    {
-      err = gpg_err_make (default_errsource,
-                          (hnf? GPG_ERR_UNKNOWN_HOST
-                              : gpg_err_code_from_syserror ()));
-      xfree (hd);
-      return err;
-    }
-  hd->sock = my_socket_new (sock);
-  if (!hd->sock)
-    {
-      err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
-      xfree (hd);
-      return err;
-    }
+  {
+    assuan_fd_t sock;
+
+    sock = connect_server (server, port, hd->flags, srvtag, &hnf);
+    if (sock == ASSUAN_INVALID_FD)
+      {
+        err = gpg_err_make (default_errsource,
+                            (hnf? GPG_ERR_UNKNOWN_HOST
+                             : gpg_err_code_from_syserror ()));
+        xfree (hd);
+        return err;
+      }
+    hd->sock = my_socket_new (sock);
+    if (!hd->sock)
+      {
+        err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+        xfree (hd);
+        return err;
+      }
+  }
 
   /* Setup estreams for reading and writing.  */
   cookie = xtrycalloc (1, sizeof *cookie);
@@ -1073,6 +1084,7 @@ do_parse_uri (parsed_uri_t uri, int only_local_part,
   uri->is_http = 0;
   uri->opaque = 0;
   uri->v6lit = 0;
+  uri->onion = 0;
 
   /* A quick validity check. */
   if (strspn (p, VALID_URI_CHARS) != n)
@@ -1159,49 +1171,54 @@ do_parse_uri (parsed_uri_t uri, int only_local_part,
         {
           uri->opaque = 1;
           uri->path = p;
+          if (is_onion_address (uri->path))
+            uri->onion = 1;
           return 0;
         }
 
     } /* End global URI part. */
 
-  /* Parse the pathname part */
-  if (!p || !*p)
-    return 0;  /* We don't have a path.  Okay. */
-
-  /* TODO: Here we have to check params. */
-
-  /* Do we have a query part? */
-  if ((p2 = strchr (p, '?')))
-    *p2++ = 0;
-
-  uri->path = p;
-  if ((n = remove_escapes (p)) < 0)
-    return GPG_ERR_BAD_URI;
-  if (n != strlen (p))
-    return GPG_ERR_BAD_URI;    /* Path includes a Nul. */
-  p = p2 ? p2 : NULL;
-
-  if (!p || !*p)
-    return 0; /* We don't have a query string.  Okay. */
-
-  /* Now parse the query string. */
-  tail = &uri->query;
-  for (;;)
+  /* Parse the pathname part if any.  */
+  if (p && *p)
     {
-      uri_tuple_t elem;
+      /* TODO: Here we have to check params. */
 
-      if ((p2 = strchr (p, '&')))
-       *p2++ = 0;
-      if (!(elem = parse_tuple (p)))
-       return GPG_ERR_BAD_URI;
-      *tail = elem;
-      tail = &elem->next;
+      /* Do we have a query part? */
+      if ((p2 = strchr (p, '?')))
+        *p2++ = 0;
 
-      if (!p2)
-       break; /* Ready. */
-      p = p2;
+      uri->path = p;
+      if ((n = remove_escapes (p)) < 0)
+        return GPG_ERR_BAD_URI;
+      if (n != strlen (p))
+        return GPG_ERR_BAD_URI;        /* Path includes a Nul. */
+      p = p2 ? p2 : NULL;
+
+      /* Parse a query string if any.  */
+      if (p && *p)
+        {
+          tail = &uri->query;
+          for (;;)
+            {
+              uri_tuple_t elem;
+
+              if ((p2 = strchr (p, '&')))
+                *p2++ = 0;
+              if (!(elem = parse_tuple (p)))
+                return GPG_ERR_BAD_URI;
+              *tail = elem;
+              tail = &elem->next;
+
+              if (!p2)
+                break; /* Ready. */
+              p = p2;
+            }
+        }
     }
 
+  if (is_onion_address (uri->host))
+    uri->onion = 1;
+
   return 0;
 }
 
@@ -1460,8 +1477,13 @@ send_request (http_t hd, const char *httphost, const char *auth,
 
   if ((hd->flags & HTTP_FLAG_FORCE_TOR))
     {
-      log_error ("TOR support is not yet available\n");
-      return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
+      int mode;
+
+      if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
+        {
+          log_error ("Tor support is not available\n");
+          return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
+        }
     }
 
   server = *hd->uri->host ? hd->uri->host : "localhost";
@@ -1560,7 +1582,7 @@ send_request (http_t hd, const char *httphost, const char *auth,
                              hd->flags, srvtag, &hnf);
       save_errno = errno;
       http_release_parsed_uri (uri);
-      if (sock == -1)
+      if (sock == ASSUAN_INVALID_FD)
         gpg_err_set_errno (save_errno);
     }
   else
@@ -1568,7 +1590,7 @@ send_request (http_t hd, const char *httphost, const char *auth,
       sock = connect_server (server, port, hd->flags, srvtag, &hnf);
     }
 
-  if (sock == -1)
+  if (sock == ASSUAN_INVALID_FD)
     {
       xfree (proxy_authstr);
       return gpg_err_make (default_errsource,
@@ -1621,12 +1643,10 @@ send_request (http_t hd, const char *httphost, const char *auth,
 
       my_socket_ref (hd->sock);
       gnutls_transport_set_ptr (hd->session->tls_session, hd->sock);
-#ifdef USE_NPTH
       gnutls_transport_set_pull_function (hd->session->tls_session,
-                                          my_npth_read);
+                                          my_gnutls_read);
       gnutls_transport_set_push_function (hd->session->tls_session,
-                                          my_npth_write);
-#endif
+                                          my_gnutls_write);
 
       do
         {
@@ -2183,66 +2203,70 @@ start_server ()
 
 /* Actually connect to a server.  Returns the file descriptor or -1 on
    error.  ERRNO is set on error. */
-static int
+static assuan_fd_t
 connect_server (const char *server, unsigned short port,
                 unsigned int flags, const char *srvtag, int *r_host_not_found)
 {
-  int sock = -1;
+  gpg_error_t err;
+  assuan_fd_t sock = ASSUAN_INVALID_FD;
   int srvcount = 0;
   int hostfound = 0;
   int anyhostaddr = 0;
   int srv, connected;
   int last_errno = 0;
   struct srventry *serverlist = NULL;
-#ifdef HAVE_W32_SYSTEM
-  unsigned long inaddr;
-#endif
+  int ret;
 
   *r_host_not_found = 0;
-#ifdef HAVE_W32_SYSTEM
-
-#ifndef HTTP_NO_WSASTARTUP
+#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
   init_sockets ();
-#endif
-  /* Win32 gethostbyname doesn't handle IP addresses internally, so we
-     try inet_addr first on that platform only. */
-  inaddr = inet_addr(server);
-  if ( inaddr != INADDR_NONE )
+#endif /*Windows*/
+
+  /* Onion addresses require special treatment.  */
+  if (is_onion_address (server))
     {
-      struct sockaddr_in addr;
+#ifdef ASSUAN_SOCK_TOR
 
-      memset(&addr,0,sizeof(addr));
+      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));
+        }
+      return sock;
 
-      sock = socket(AF_INET,SOCK_STREAM,0);
-      if ( sock==INVALID_SOCKET )
-       {
-         log_error("error creating socket: ec=%d\n",(int)WSAGetLastError());
-         return -1;
-       }
+#else /*!ASSUAN_SOCK_TOR*/
 
-      addr.sin_family = AF_INET;
-      addr.sin_port = htons(port);
-      memcpy (&addr.sin_addr,&inaddr,sizeof(inaddr));
+      gpg_err_set_errno (ENETUNREACH);
+      return -1; /* Out of core.  */
 
-      if (!my_connect (sock,(struct sockaddr *)&addr,sizeof(addr)) )
-       return sock;
-      sock_close(sock);
-      return -1;
+#endif /*!HASSUAN_SOCK_TOR*/
     }
-#endif /*HAVE_W32_SYSTEM*/
 
 #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 <= MAXDNAME)
+      if (1 + strlen (srvtag) + 6 + strlen (server) + 1
+          <= DIMof (struct srventry, target))
        {
-         char srvname[MAXDNAME];
+         char *srvname = xtrymalloc (DIMof (struct srventry, target));
 
-         stpcpy (stpcpy (stpcpy (stpcpy (srvname,"_"), srvtag),
-                           "._tcp."), server);
-         srvcount = getsrv (srvname, &serverlist);
+          if (!srvname) /* Out of core */
+            {
+              serverlist = NULL;
+              srvcount = 0;
+            }
+          else
+            {
+              stpcpy (stpcpy (stpcpy (stpcpy (srvname,"_"), srvtag),
+                              "._tcp."), server);
+              srvcount = getsrv (srvname, &serverlist);
+              xfree (srvname);
+            }
        }
     }
 #else
@@ -2258,111 +2282,55 @@ connect_server (const char *server, unsigned short port,
       if (!serverlist)
         return -1; /* Out of core.  */
       serverlist->port = port;
-      strncpy (serverlist->target, server, MAXDNAME);
-      serverlist->target[MAXDNAME-1] = '\0';
+      strncpy (serverlist->target, server, DIMof (struct srventry, target));
+      serverlist->target[DIMof (struct srventry, target)-1] = '\0';
       srvcount = 1;
     }
 
-#ifdef HAVE_GETADDRINFO
   connected = 0;
   for (srv=0; srv < srvcount && !connected; srv++)
     {
-      struct addrinfo hints, *res, *ai;
-      char portstr[35];
+      dns_addrinfo_t aibuf, ai;
 
-      snprintf (portstr, sizeof portstr, "%hu", port);
-      memset (&hints, 0, sizeof (hints));
-      hints.ai_socktype = SOCK_STREAM;
-      if (getaddrinfo (serverlist[srv].target, portstr, &hints, &res))
-        continue; /* Not found - try next one. */
+      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));
+          continue; /* Not found - try next one. */
+        }
       hostfound = 1;
 
-      for (ai = res; ai && !connected; ai = ai->ai_next)
+      for (ai = aibuf; ai && !connected; ai = ai->next)
         {
-          if (ai->ai_family == AF_INET && (flags & HTTP_FLAG_IGNORE_IPv4))
+          if (ai->family == AF_INET && (flags & HTTP_FLAG_IGNORE_IPv4))
             continue;
-          if (ai->ai_family == AF_INET6 && (flags & HTTP_FLAG_IGNORE_IPv6))
+          if (ai->family == AF_INET6 && (flags & HTTP_FLAG_IGNORE_IPv6))
             continue;
 
-          if (sock != -1)
-            sock_close (sock);
-          sock = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol);
-          if (sock == -1)
+          if (sock != ASSUAN_INVALID_FD)
+            assuan_sock_close (sock);
+          sock = assuan_sock_new (ai->family, ai->socktype, ai->protocol);
+          if (sock == ASSUAN_INVALID_FD)
             {
               int save_errno = errno;
               log_error ("error creating socket: %s\n", strerror (errno));
-              freeaddrinfo (res);
+              free_dns_addrinfo (aibuf);
               xfree (serverlist);
               errno = save_errno;
-              return -1;
+              return ASSUAN_INVALID_FD;
             }
 
           anyhostaddr = 1;
-          if (my_connect (sock, ai->ai_addr, ai->ai_addrlen))
+          ret = assuan_sock_connect (sock, ai->addr, ai->addrlen);
+          if (ret)
             last_errno = errno;
           else
             connected = 1;
         }
-      freeaddrinfo (res);
-    }
-#else /* !HAVE_GETADDRINFO */
-  connected = 0;
-  for (srv=0; srv < srvcount && !connected; srv++)
-    {
-      int i;
-      struct hostent *host = NULL;
-      struct sockaddr_in addr;
-
-      /* Note: This code is not thread-safe.  */
-
-      memset (&addr, 0, sizeof (addr));
-      host = gethostbyname (serverlist[srv].target);
-      if (!host)
-        continue;
-      hostfound = 1;
-
-      if (sock != -1)
-        sock_close (sock);
-      sock = socket (host->h_addrtype, SOCK_STREAM, 0);
-      if (sock == -1)
-        {
-          log_error ("error creating socket: %s\n", strerror (errno));
-          xfree (serverlist);
-          return -1;
-        }
-
-      addr.sin_family = host->h_addrtype;
-      if (addr.sin_family != AF_INET)
-       {
-         log_error ("unknown address family for '%s'\n",
-                     serverlist[srv].target);
-          xfree (serverlist);
-         return -1;
-       }
-      addr.sin_port = htons (serverlist[srv].port);
-      if (host->h_length != 4)
-        {
-          log_error ("illegal address length for '%s'\n",
-                     serverlist[srv].target);
-          xfree (serverlist);
-          return -1;
-        }
-
-      /* Try all A records until one responds. */
-      for (i = 0; host->h_addr_list[i] && !connected; i++)
-        {
-          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;
-          else
-            {
-              connected = 1;
-              break;
-            }
-        }
+      free_dns_addrinfo (aibuf);
     }
-#endif /* !HAVE_GETADDRINFO */
 
   xfree (serverlist);
 
@@ -2386,10 +2354,10 @@ connect_server (const char *server, unsigned short port,
         }
       if (!hostfound || (hostfound && !anyhostaddr))
         *r_host_not_found = 1;
-      if (sock != -1)
-       sock_close (sock);
+      if (sock != ASSUAN_INVALID_FD)
+       assuan_sock_close (sock);
       gpg_err_set_errno (last_errno);
-      return -1;
+      return ASSUAN_INVALID_FD;
     }
   return sock;
 }
@@ -2450,7 +2418,7 @@ write_server (int sock, const char *data, size_t length)
 
 \f
 /* Read handler for estream.  */
-static ssize_t
+static gpgrt_ssize_t
 cookie_read (void *cookie, void *buffer, size_t size)
 {
   cookie_t c = cookie;
@@ -2484,6 +2452,13 @@ cookie_read (void *cookie, void *buffer, size_t size)
             }
           if (nread == GNUTLS_E_REHANDSHAKE)
             goto again; /* A client is allowed to just ignore this request. */
+          if (nread == GNUTLS_E_PREMATURE_TERMINATION)
+            {
+              /* The server terminated the connection.  Close the TLS
+                 session, and indicate EOF using a short read.  */
+              close_tls_session (c->session);
+              return 0;
+            }
           log_info ("TLS network read failed: %s\n", gnutls_strerror (nread));
           gpg_err_set_errno (EIO);
           return -1;
@@ -2525,11 +2500,11 @@ cookie_read (void *cookie, void *buffer, size_t size)
         c->content_length = 0;
     }
 
-  return nread;
+  return (gpgrt_ssize_t)nread;
 }
 
 /* Write handler for estream.  */
-static ssize_t
+static gpgrt_ssize_t
 cookie_write (void *cookie, const void *buffer_arg, size_t size)
 {
   const char *buffer = buffer_arg;
@@ -2578,7 +2553,7 @@ cookie_write (void *cookie, const void *buffer_arg, size_t size)
         nwritten = size;
     }
 
-  return nwritten;
+  return (gpgrt_ssize_t)nwritten;
 }