fix only cache variable long/short
[oweals/gnunet.git] / src / gns / gnunet-gns-proxy.c
index a28ab98dcdb34891f96abac7da4f544838886bdf..e65cd61101c115e4e64fec26498700b9d59a67ef 100644 (file)
@@ -26,7 +26,6 @@
  *
  * TODO:
  * - double-check queueing logic
- * - actually check SSL certificates (#3038)
  */
 #include "platform.h"
 #include <microhttpd.h>
@@ -35,6 +34,9 @@
 #include <gnutls/x509.h>
 #include <gnutls/abstract.h>
 #include <gnutls/crypto.h>
+#if HAVE_GNUTLS_DANE
+#include <gnutls/dane.h>
+#endif
 #include <regex.h>
 #include "gnunet_util_lib.h"
 #include "gnunet_gns_service.h"
@@ -501,6 +503,11 @@ struct Socks5Request
    */
   char *leho;
 
+  /**
+   * Payload of the (last) DANE record encountered.
+   */
+  char *dane_data;
+
   /**
    * The URL to fetch
    */
@@ -521,6 +528,11 @@ struct Socks5Request
    */
   unsigned int response_code;
 
+  /**
+   * Number of bytes in @e dane_data.
+   */
+  size_t dane_data_len;
+
   /**
    * Number of bytes already in read buffer
    */
@@ -569,14 +581,24 @@ static unsigned long port = GNUNET_GNS_PROXY_PORT;
 static char *cafile_opt;
 
 /**
- * The listen socket of the proxy
+ * The listen socket of the proxy for IPv4
+ */
+static struct GNUNET_NETWORK_Handle *lsock4;
+
+/**
+ * The listen socket of the proxy for IPv6
+ */
+static struct GNUNET_NETWORK_Handle *lsock6;
+
+/**
+ * The listen task ID for IPv4
  */
-static struct GNUNET_NETWORK_Handle *lsock;
+static GNUNET_SCHEDULER_TaskIdentifier ltask4;
 
 /**
- * The listen task ID
+ * The listen task ID for IPv6
  */
-static GNUNET_SCHEDULER_TaskIdentifier ltask;
+static GNUNET_SCHEDULER_TaskIdentifier ltask6;
 
 /**
  * The cURL download task (curl multi API).
@@ -715,6 +737,7 @@ cleanup_s5r (struct Socks5Request *s5r)
   GNUNET_free_non_null (s5r->domain);
   GNUNET_free_non_null (s5r->leho);
   GNUNET_free_non_null (s5r->url);
+  GNUNET_free_non_null (s5r->dane_data);
   GNUNET_free (s5r);
 }
 
@@ -790,44 +813,146 @@ mhd_content_cb (void *cls,
 static int
 check_ssl_certificate (struct Socks5Request *s5r)
 {
-  unsigned int i;
-  union {
-    gnutls_session_t session;
-    struct curl_slist    * to_slist;
-  } gptr;
   unsigned int cert_list_size;
   const gnutls_datum_t *chainp;
+  const struct curl_tlssessioninfo *tlsinfo;
+  char certdn[GNUNET_DNSPARSER_MAX_NAME_LENGTH + 3];
+  size_t size;
+  gnutls_x509_crt_t x509_cert;
+  int rc;
+  const char *name;
 
-  gptr.to_slist = NULL;
   if (CURLE_OK !=
       curl_easy_getinfo (s5r->curl,
-                        CURLINFO_GNUTLS_SESSION,
-                        &gptr))
+                        CURLINFO_TLS_SESSION,
+                        (struct curl_slist **) &tlsinfo))
+    return GNUNET_SYSERR;
+  if (CURLSSLBACKEND_GNUTLS != tlsinfo->backend)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                _("Unsupported CURL SSL backend %d\n"),
+                tlsinfo->backend);
+    return GNUNET_SYSERR;
+  }
+  chainp = gnutls_certificate_get_peers (tlsinfo->internals, &cert_list_size);
+  if ( (! chainp) || (0 == cert_list_size) )
     return GNUNET_SYSERR;
 
-  chainp = gnutls_certificate_get_peers(gptr.session, &cert_list_size);
-  if(!chainp)
+  size = sizeof (certdn);
+  /* initialize an X.509 certificate structure. */
+  gnutls_x509_crt_init (&x509_cert);
+  gnutls_x509_crt_import (x509_cert,
+                          chainp,
+                          GNUTLS_X509_FMT_DER);
+
+  if (0 != (rc = gnutls_x509_crt_get_dn_by_oid (x509_cert,
+                                                GNUTLS_OID_X520_COMMON_NAME,
+                                                0, /* the first and only one */
+                                                0 /* no DER encoding */,
+                                                certdn,
+                                                &size)))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                _("Failed to fetch CN from cert: %s\n"),
+                gnutls_strerror(rc));
+    gnutls_x509_crt_deinit (x509_cert);
     return GNUNET_SYSERR;
+  }
+  /* check for TLSA/DANE records */
+#if HAVE_GNUTLS_DANE
+  if (NULL != s5r->dane_data)
+  {
+    char *dd[] = { s5r->dane_data, NULL };
+    int dlen[] = { s5r->dane_data_len, 0};
+    dane_state_t dane_state;
+    dane_query_t dane_query;
+    unsigned int verify;
 
-  for(i=0;i<cert_list_size;i++) {
-    gnutls_x509_crt_t cert;
-    gnutls_datum_t dn;
-
-    if(GNUTLS_E_SUCCESS == gnutls_x509_crt_init (&cert)) {
-      if((GNUTLS_E_SUCCESS ==
-         gnutls_x509_crt_import (cert, &chainp[i],
-                                 GNUTLS_X509_FMT_DER)) &&
-        (GNUTLS_E_SUCCESS ==
-         gnutls_x509_crt_print (cert,
-                                GNUTLS_CRT_PRINT_FULL,
-                                &dn))) {
-       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                   "Certificate #%d: %.*s", i, dn.size, dn.data);
-       gnutls_free (dn.data);
-        gnutls_x509_crt_deinit (cert);
+    /* FIXME: add flags to gnutls to NOT read UNBOUND_ROOT_KEY_FILE here! */
+    if (0 != (rc = dane_state_init (&dane_state,
+#ifdef DANE_F_IGNORE_DNSSEC
+                                    DANE_F_IGNORE_DNSSEC |
+#endif
+                                    DANE_F_IGNORE_LOCAL_RESOLVER)))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  _("Failed to initialize DANE: %s\n"),
+                  dane_strerror(rc));
+      gnutls_x509_crt_deinit (x509_cert);
+      return GNUNET_SYSERR;
+    }
+    if (0 != (rc = dane_raw_tlsa (dane_state,
+                                  &dane_query,
+                                  dd,
+                                  dlen,
+                                  GNUNET_YES,
+                                  GNUNET_NO)))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  _("Failed to parse DANE record: %s\n"),
+                  dane_strerror(rc));
+      dane_state_deinit (dane_state);
+      gnutls_x509_crt_deinit (x509_cert);
+      return GNUNET_SYSERR;
+    }
+    if (0 != (rc = dane_verify_crt_raw (dane_state,
+                                        chainp,
+                                        cert_list_size,
+                                        gnutls_certificate_type_get (tlsinfo->internals),
+                                        dane_query,
+                                        0, 0,
+                                        &verify)))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  _("Failed to verify TLS connection using DANE: %s\n"),
+                  dane_strerror(rc));
+      dane_query_deinit (dane_query);
+      dane_state_deinit (dane_state);
+      gnutls_x509_crt_deinit (x509_cert);
+      return GNUNET_SYSERR;
+    }
+    if (0 != verify)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  _("Failed DANE verification failed with GnuTLS verify status code: %u\n"),
+                  verify);
+      dane_query_deinit (dane_query);
+      dane_state_deinit (dane_state);
+      gnutls_x509_crt_deinit (x509_cert);
+      return GNUNET_SYSERR;
+    }
+    dane_query_deinit (dane_query);
+    dane_state_deinit (dane_state);
+    /* success! */
+  }
+  else
+#endif
+  {
+    /* try LEHO or ordinary domain name X509 verification */
+    name = s5r->domain;
+    if (NULL != s5r->leho)
+      name = s5r->leho;
+    if (NULL != name)
+    {
+      if (0 == (rc = gnutls_x509_crt_check_hostname (x509_cert,
+                                                     name)))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                    _("SSL certificate subject name (%s) does not match `%s'\n"),
+                    certdn,
+                    name);
+        gnutls_x509_crt_deinit (x509_cert);
+        return GNUNET_SYSERR;
       }
     }
+    else
+    {
+      /* we did not even have the domain name!? */
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
   }
+  gnutls_x509_crt_deinit (x509_cert);
   return GNUNET_OK;
 }
 
@@ -1212,7 +1337,7 @@ curl_task_download (void *cls,
       GNUNET_break (CURLE_OK ==
                    curl_easy_getinfo (msg->easy_handle,
                                       CURLINFO_PRIVATE,
-                                      &s5r));
+                                      (char **) &s5r ));
       if (NULL == s5r)
       {
        GNUNET_break (0);
@@ -1273,7 +1398,7 @@ curl_task_download (void *cls,
                "Suspending cURL multi loop, no more events pending\n");
     return; /* nothing more in progress */
   }
-  curl_download_prepare();
+  curl_download_prepare ();
 }
 
 
@@ -1354,7 +1479,6 @@ create_response (void *cls,
                  size_t *upload_data_size,
                  void **con_cls)
 {
-  /* struct MhdHttpList* hd = cls;  */
   struct Socks5Request *s5r = *con_cls;
   char *curlurl;
   char ipstring[INET6_ADDRSTRLEN];
@@ -1429,7 +1553,7 @@ create_response (void *cls,
     curl_easy_setopt (s5r->curl, CURLOPT_HTTP_TRANSFER_DECODING, 0);
     curl_easy_setopt (s5r->curl, CURLOPT_NOSIGNAL, 1L);
     curl_easy_setopt (s5r->curl, CURLOPT_PRIVATE, s5r);
-    curl_easy_setopt (s5r->curl, CURLOPT_VERBOSE, 0); // FIXME: remove later
+    curl_easy_setopt (s5r->curl, CURLOPT_VERBOSE, 0);
     GNUNET_asprintf (&curlurl,
                     (HTTPS_PORT != s5r->port)
                     ? "http://%s:%d%s"
@@ -1630,6 +1754,7 @@ mhd_log_callback (void *cls,
       return s5r;
     }
   }
+  GNUNET_break (0);
   return NULL;
 }
 
@@ -1838,6 +1963,8 @@ load_key_from_file (gnutls_x509_privkey_t key,
   int ret;
 
   key_data.data = load_file (keyfile, &key_data.size);
+  if (NULL == key_data.data)
+    return GNUNET_SYSERR;
   ret = gnutls_x509_privkey_import (key, &key_data,
                                     GNUTLS_X509_FMT_PEM);
   if (GNUTLS_E_SUCCESS != ret)
@@ -1866,6 +1993,8 @@ load_cert_from_file (gnutls_x509_crt_t crt,
   int ret;
 
   cert_data.data = load_file (certfile, &cert_data.size);
+  if (NULL == cert_data.data)
+    return GNUNET_SYSERR;
   ret = gnutls_x509_crt_import (crt, &cert_data,
                                 GNUTLS_X509_FMT_PEM);
   if (GNUTLS_E_SUCCESS != ret)
@@ -1902,7 +2031,7 @@ generate_gns_certificate (const char *name)
   GNUNET_break (GNUTLS_E_SUCCESS == gnutls_x509_crt_set_key (request, proxy_ca.key));
   pgc = GNUNET_new (struct ProxyGNSCertificate);
   gnutls_x509_crt_set_dn_by_oid (request, GNUTLS_OID_X520_COUNTRY_NAME,
-                                 0, "TNR", 2);
+                                 0, "ZZ", 2);
   gnutls_x509_crt_set_dn_by_oid (request, GNUTLS_OID_X520_ORGANIZATION_NAME,
                                  0, "GNU Name System", 4);
   gnutls_x509_crt_set_dn_by_oid (request, GNUTLS_OID_X520_COMMON_NAME,
@@ -1934,11 +2063,27 @@ generate_gns_certificate (const char *name)
 }
 
 
+/**
+ * Function called by MHD with errors, suppresses them all.
+ *
+ * @param cls closure
+ * @param fm format string (`printf()`-style)
+ * @param ap arguments to @a fm
+ */
+static void
+mhd_error_log_callback (void *cls,
+                        const char *fm,
+                        va_list ap)
+{
+  /* do nothing */
+}
+
+
 /**
  * Lookup (or create) an SSL MHD instance for a particular domain.
  *
  * @param domain the domain the SSL daemon has to serve
- * @return NULL on errro
+ * @return NULL on error
  */
 static struct MhdHttpList *
 lookup_ssl_httpd (const char* domain)
@@ -1946,6 +2091,11 @@ lookup_ssl_httpd (const char* domain)
   struct MhdHttpList *hd;
   struct ProxyGNSCertificate *pgc;
 
+  if (NULL == domain)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
   for (hd = mhd_httpd_head; NULL != hd; hd = hd->next)
     if ( (NULL != hd->domain) &&
         (0 == strcmp (hd->domain, domain)) )
@@ -1965,6 +2115,7 @@ lookup_ssl_httpd (const char* domain)
                                 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16,
                                 MHD_OPTION_NOTIFY_COMPLETED, &mhd_completed_cb, NULL,
                                 MHD_OPTION_URI_LOG_CALLBACK, &mhd_log_callback, NULL,
+                                 MHD_OPTION_EXTERNAL_LOGGER, &mhd_error_log_callback, NULL,
                                 MHD_OPTION_HTTPS_MEM_KEY, pgc->key,
                                 MHD_OPTION_HTTPS_MEM_CERT, pgc->cert,
                                 MHD_OPTION_END);
@@ -2181,11 +2332,11 @@ signal_socks_success (struct Socks5Request *s5r)
 static void
 handle_gns_result (void *cls,
                   uint32_t rd_count,
-                  const struct GNUNET_NAMESTORE_RecordData *rd)
+                  const struct GNUNET_GNSRECORD_Data *rd)
 {
   struct Socks5Request *s5r = cls;
   uint32_t i;
-  const struct GNUNET_NAMESTORE_RecordData *r;
+  const struct GNUNET_GNSRECORD_Data *r;
   int got_ip;
 
   s5r->gns_lookup = NULL;
@@ -2248,14 +2399,22 @@ handle_gns_result (void *cls,
 #endif
       }
       break;
-    case GNUNET_NAMESTORE_TYPE_VPN:
+    case GNUNET_GNSRECORD_TYPE_VPN:
       GNUNET_break (0); /* should have been translated within GNS */
       break;
-    case GNUNET_NAMESTORE_TYPE_LEHO:
+    case GNUNET_GNSRECORD_TYPE_LEHO:
       GNUNET_free_non_null (s5r->leho);
       s5r->leho = GNUNET_strndup (r->data,
                                  r->data_size);
       break;
+    case GNUNET_DNSPARSER_TYPE_TLSA:
+      GNUNET_free_non_null (s5r->dane_data);
+      s5r->dane_data_len = r->data_size;
+      s5r->dane_data = GNUNET_malloc (r->data_size);
+      memcpy (s5r->dane_data,
+              r->data,
+              r->data_size);
+      break;
     default:
       /* don't care */
       break;
@@ -2386,6 +2545,14 @@ do_s5r_read (void *cls,
        struct sockaddr_in *in;
 
        s5r->port = ntohs (*port);
+        if (HTTPS_PORT == s5r->port)
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      _("SSL connection to plain IPv4 address requested\n"));
+          signal_socks_failure (s5r,
+                                SOCKS5_STATUS_CONNECTION_NOT_ALLOWED_BY_RULE);
+          return;
+        }
        alen = sizeof (struct in_addr);
        if (s5r->rbuf_len < sizeof (struct Socks5ClientRequestMessage) +
            alen + sizeof (uint16_t))
@@ -2407,6 +2574,14 @@ do_s5r_read (void *cls,
        struct sockaddr_in6 *in;
 
        s5r->port = ntohs (*port);
+        if (HTTPS_PORT == s5r->port)
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      _("SSL connection to plain IPv4 address requested\n"));
+          signal_socks_failure (s5r,
+                                SOCKS5_STATUS_CONNECTION_NOT_ALLOWED_BY_RULE);
+          return;
+        }
        alen = sizeof (struct in6_addr);
        if (s5r->rbuf_len < sizeof (struct Socks5ClientRequestMessage) +
            alen + sizeof (uint16_t))
@@ -2495,22 +2670,31 @@ do_s5r_read (void *cls,
 /**
  * Accept new incoming connections
  *
- * @param cls the closure
+ * @param cls the closure with the lsock4 or lsock6
  * @param tc the scheduler context
  */
 static void
 do_accept (void *cls,
           const struct GNUNET_SCHEDULER_TaskContext *tc)
 {
+  struct GNUNET_NETWORK_Handle *lsock = cls;
   struct GNUNET_NETWORK_Handle *s;
   struct Socks5Request *s5r;
 
-  ltask = GNUNET_SCHEDULER_NO_TASK;
+  if (lsock == lsock4)
+    ltask4 = GNUNET_SCHEDULER_NO_TASK;
+  else
+    ltask6 = GNUNET_SCHEDULER_NO_TASK;
   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
     return;
-  ltask = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
-                                         lsock,
-                                         &do_accept, NULL);
+  if (lsock == lsock4)
+    ltask4 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
+                                            lsock,
+                                            &do_accept, lsock);
+  else
+    ltask6 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
+                                            lsock,
+                                            &do_accept, lsock);
   s = GNUNET_NETWORK_socket_accept (lsock, NULL, NULL);
   if (NULL == s)
   {
@@ -2550,10 +2734,15 @@ do_shutdown (void *cls,
     kill_httpd (mhd_httpd_head);
   while (NULL != s5r_head)
     cleanup_s5r (s5r_head);
-  if (NULL != lsock)
+  if (NULL != lsock4)
   {
-    GNUNET_NETWORK_socket_close (lsock);
-    lsock = NULL;
+    GNUNET_NETWORK_socket_close (lsock4);
+    lsock4 = NULL;
+  }
+  if (NULL != lsock6)
+  {
+    GNUNET_NETWORK_socket_close (lsock6);
+    lsock6 = NULL;
   }
   if (NULL != id_op)
   {
@@ -2580,10 +2769,15 @@ do_shutdown (void *cls,
     GNUNET_SCHEDULER_cancel (curl_download_task);
     curl_download_task = GNUNET_SCHEDULER_NO_TASK;
   }
-  if (GNUNET_SCHEDULER_NO_TASK != ltask)
+  if (GNUNET_SCHEDULER_NO_TASK != ltask4)
+  {
+    GNUNET_SCHEDULER_cancel (ltask4);
+    ltask4 = GNUNET_SCHEDULER_NO_TASK;
+  }
+  if (GNUNET_SCHEDULER_NO_TASK != ltask6)
   {
-    GNUNET_SCHEDULER_cancel (ltask);
-    ltask = GNUNET_SCHEDULER_NO_TASK;
+    GNUNET_SCHEDULER_cancel (ltask6);
+    ltask6 = GNUNET_SCHEDULER_NO_TASK;
   }
   gnutls_x509_crt_deinit (proxy_ca.cert);
   gnutls_x509_privkey_deinit (proxy_ca.key);
@@ -2672,24 +2866,46 @@ run_cont ()
   struct MhdHttpList *hd;
 
   /* Open listen socket for socks proxy */
-  lsock = bind_v6 ();
-  if (NULL == lsock)
-    lsock = bind_v4 ();
-  if (NULL == lsock)
+  lsock6 = bind_v6 ();
+  if (NULL == lsock6)
+    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "bind");
+  else
   {
+    if (GNUNET_OK != GNUNET_NETWORK_socket_listen (lsock6, 5))
+    {
+      GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "listen");
+      GNUNET_NETWORK_socket_close (lsock6);
+      lsock6 = NULL;
+    }
+    else
+    {
+      ltask6 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
+                                              lsock6, &do_accept, lsock6);
+    }
+  }
+  lsock4 = bind_v4 ();
+  if (NULL == lsock4)
     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "bind");
-    GNUNET_SCHEDULER_shutdown ();
-    return;
+  else
+  {
+    if (GNUNET_OK != GNUNET_NETWORK_socket_listen (lsock4, 5))
+    {
+      GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "listen");
+      GNUNET_NETWORK_socket_close (lsock4);
+      lsock4 = NULL;
+    }
+    else
+    {
+      ltask4 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
+                                              lsock4, &do_accept, lsock4);
+    }
   }
-  if (GNUNET_OK != GNUNET_NETWORK_socket_listen (lsock, 5))
+  if ( (NULL == lsock4) &&
+       (NULL == lsock6) )
   {
-    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "listen");
     GNUNET_SCHEDULER_shutdown ();
     return;
   }
-  ltask = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
-                                         lsock, &do_accept, NULL);
-
   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
@@ -2821,6 +3037,7 @@ run (void *cls, char *const *args, const char *cfgfile,
   char* cafile;
 
   cfg = c;
+
   if (NULL == (curl_multi = curl_multi_init ()))
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,