CHROMIUM: tlsdate: Support IPv6-only networks

This CL adds a workaround for BIO connection since  BIO_s_connect()
doens't support IPv4 yet. It's based on CL:717021 that introduced
IPv6 support to tlsdate but broke proxy.

TEST=tlsdate is able to sync with an IPv6 connection and proxy connection
BUG=b:63137232

Change-Id: Ia99b1c47acf6a12832b7f0b98fd15216893f8c33
Reviewed-on: https://chromium-review.googlesource.com/1541335
Commit-Ready: Pavol Marko <pmarko@chromium.org>
Tested-by: Andreea-Elena Costinas <acostinas@google.com>
Reviewed-by: Hugo Benichi <hugobenichi@google.com>
Reviewed-by: Ben Chan <benchan@chromium.org>
diff --git a/src/proxy-bio-unittest.c b/src/proxy-bio-unittest.c
index f01ba02..8b72563 100644
--- a/src/proxy-bio-unittest.c
+++ b/src/proxy-bio-unittest.c
@@ -32,8 +32,8 @@
 {
   BIO *proxy = BIO_new_proxy();
   BIO_proxy_set_type (proxy, type);
-  BIO_proxy_set_host (proxy, kTestHost);
-  BIO_proxy_set_port (proxy, TEST_PORT);
+  BIO_proxy_set_target_host (proxy, kTestHost);
+  BIO_proxy_set_target_port (proxy, TEST_PORT);
   BIO_push (proxy, test);
   return proxy;
 }
diff --git a/src/proxy-bio.c b/src/proxy-bio.c
index 006e9b4..8da423f 100644
--- a/src/proxy-bio.c
+++ b/src/proxy-bio.c
@@ -398,7 +398,7 @@
   return 0;
 }
 
-int API BIO_proxy_set_host (BIO *b, const char *host)
+int API BIO_proxy_set_target_host (BIO *b, const char *host)
 {
   struct proxy_ctx *ctx = b->ptr;
   if (strnlen (host, NI_MAXHOST) == NI_MAXHOST)
@@ -407,7 +407,7 @@
   return 0;
 }
 
-void API BIO_proxy_set_port (BIO *b, uint16_t port)
+void API BIO_proxy_set_target_port (BIO *b, uint16_t port)
 {
   struct proxy_ctx *ctx = b->ptr;
   ctx->port = port;
diff --git a/src/proxy-bio.h b/src/proxy-bio.h
index 099daef..b46df80 100644
--- a/src/proxy-bio.h
+++ b/src/proxy-bio.h
@@ -19,7 +19,7 @@
 
 /* These do not take ownership of their string arguments. */
 int BIO_proxy_set_type (BIO *b, const char *type);
-int BIO_proxy_set_host (BIO *b, const char *host);
-void BIO_proxy_set_port (BIO *b, uint16_t port);
+int BIO_proxy_set_target_host (BIO *b, const char *host);
+void BIO_proxy_set_target_port (BIO *b, uint16_t port);
 
 #endif /* !PROXY_BIO_H */
diff --git a/src/tlsdate-helper.c b/src/tlsdate-helper.c
index 4ffabdf..6830ef8 100644
--- a/src/tlsdate-helper.c
+++ b/src/tlsdate-helper.c
@@ -75,12 +75,35 @@
  */
 
 #include "config.h"
+
 #include "src/tlsdate-helper.h"
 
+#include <errno.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
 #include "src/proxy-bio.h"
 #include "src/util.h"
 #include "src/compat/clock.h"
 
+static int g_ca_racket;
+
+static const char *g_ca_cert_container;
+
+// Target host to connect to.
+static const char *g_host;
+
+// Port to connect to on the target host.
+static const char *g_port;
+
+// The SSL/TLS protocol used
+static const char *g_protocol;
+
+// Proxy with format scheme://proxy_host:proxy_port.
+static char *g_proxy;
+
 static void
 validate_proxy_scheme (const char *scheme)
 {
@@ -105,11 +128,15 @@
 }
 
 static void
-validate_proxy_port (const char *port)
+validate_port (const char *port)
 {
-  while (*port)
-    if (!isdigit (*port++))
-      die ("invalid char in port\n");
+  if (!port)
+    return;
+  char *end;
+  const int kBase = 10;
+  unsigned long value = strtoul(port, &end, kBase);
+  if (errno != 0 || value > SHRT_MAX || value == 0 || *end != '\0')
+    die("invalid port %s\n", port);
 }
 
 static void
@@ -130,47 +157,118 @@
   *port = proxy;
   validate_proxy_scheme (*scheme);
   validate_proxy_host (*host);
-  validate_proxy_port (*port);
+  validate_port (*port);
 }
 
-static void
-setup_proxy (BIO *ssl)
-{
-  BIO *bio;
-  char *scheme;
-  char *proxy_host;
-  char *proxy_port;
-  if (!proxy)
-    return;
-  /*
-   * grab the proxy's host and port out of the URI we have for it. We want the
-   * underlying connect BIO to connect to this, not the target host and port, so
-   * we squirrel away the target host and port in the proxy BIO (as the proxy
-   * target) and swap out the connect BIO's target host and port so it'll
-   * connect to the proxy instead.
-   */
-  parse_proxy_uri (proxy, &scheme, &proxy_host, &proxy_port);
-  bio = BIO_new_proxy();
-  BIO_proxy_set_type (bio, scheme);
-  BIO_proxy_set_host (bio, host);
-  BIO_proxy_set_port (bio, atoi (port));
-  host = proxy_host;
-  port = proxy_port;
-  BIO_push (ssl, bio);
-}
-
+/* Returns a BIO that talks through a HTTP proxy using the CONNECT command.
+ * The created BIO will ask the proxy to CONNECT to |target_host|:|target_port|
+ * using |scheme|.
+ */
 static BIO *
-make_ssl_bio (SSL_CTX *ctx)
+BIO_create_proxy (const char *scheme, const char *target_host,
+                  const char *target_port)
 {
-  BIO *con = NULL;
-  BIO *ssl = NULL;
-  if (! (con = BIO_new (BIO_s_connect())))
-    die ("BIO_s_connect failed\n");
-  if (! (ssl = BIO_new_ssl (ctx, 1)))
+  BIO *bio_proxy = BIO_new_proxy ();
+  BIO_proxy_set_type (bio_proxy, scheme);
+  BIO_proxy_set_target_host (bio_proxy, target_host);
+  BIO_proxy_set_target_port (bio_proxy, atoi (target_port));
+  return bio_proxy;
+}
+
+/* Connects to |host| on port |port|.
+ * Returns the socket file descriptor if successful, otherwise exits with
+ * failure.
+ */
+static int create_connection (const char *host, const char *port)
+{
+  int err, sock = -1;
+  struct addrinfo *ai = NULL, *cai = NULL;
+  struct addrinfo hints = {
+      .ai_flags = AI_ADDRCONFIG,
+      .ai_family = AF_UNSPEC,
+      .ai_socktype = SOCK_STREAM,
+  };
+
+  err = getaddrinfo (host, port, &hints, &ai);
+
+  if (err != 0 || !ai)
+    die ("getaddrinfo (%s): %s\n", host, gai_strerror (err));
+
+  for (cai = ai; cai; cai = cai->ai_next)
+  {
+    sock = socket (cai->ai_family, SOCK_STREAM, 0);
+    if (sock < 0)
+    {
+      perror ("socket");
+      continue;
+    }
+
+    if (connect (sock, cai->ai_addr, cai->ai_addrlen) != 0)
+    {
+      perror ("connect");
+      close (sock);
+      sock = -1;
+      continue;
+    }
+    break;
+  }
+  freeaddrinfo (ai);
+
+  if (sock < 0)
+    die ("failed to find any remote addresses for %s:%s\n", host, port);
+
+  return sock;
+}
+
+/* Creates a BIO wrapper over the socket connection to |host|:|port|.
+ * This workaround is needed because BIO_s_connect() doesn't support IPv6.
+ */
+static BIO *BIO_create_socket (const char *host, const char *port)
+{
+  BIO *bio_socket = NULL;
+  int sockfd = create_connection (host, port);
+  if ( !(bio_socket = BIO_new_fd (sockfd, 1 /* close_flag */)))
+    die ("BIO_new_fd failed\n");
+
+  return bio_socket;
+}
+
+/* Creates an OpenSSL BIO-chain which talks to |target_host|:|target_port|
+ * using the SSL protocol. If |proxy| is set it will be used as a HTTP proxy.
+ */
+static BIO *
+make_ssl_bio (SSL_CTX *ctx, const char *target_host, const char *target_port,
+              char *proxy)
+{
+  BIO *bio_socket = NULL;
+  BIO *bio_ssl = NULL;
+  BIO *bio_proxy = NULL;
+  char *scheme = NULL;
+  char *proxy_host = NULL;
+  char *proxy_port = NULL;
+
+  if ( !(bio_ssl = BIO_new_ssl (ctx, 1)))
     die ("BIO_new_ssl failed\n");
-  setup_proxy (ssl);
-  BIO_push (ssl, con);
-  return ssl;
+
+  if (proxy)
+  {
+    verb ("V: using proxy %s\n", proxy);
+    parse_proxy_uri (proxy, &scheme, &proxy_host, &proxy_port);
+
+    if ( !(bio_proxy = BIO_create_proxy (scheme, target_host, target_port)))
+        die ("BIO_create_proxy failed\n");
+
+    bio_socket = BIO_create_socket (proxy_host, proxy_port);
+
+    BIO_push (bio_ssl, bio_proxy);
+    BIO_push (bio_ssl, bio_socket);
+  }
+  else
+  {
+    bio_socket = BIO_create_socket (target_host, target_port);
+    BIO_push (bio_ssl, bio_socket);
+  }
+  return bio_ssl;
 }
 
 /** helper function for 'malloc' */
@@ -593,7 +691,7 @@
     }
   else
     {
-      die ("hostname verification failed for host %s!\n", host);
+      die ("hostname verification failed for host %s!\n", g_host);
     }
   return ret;
 }
@@ -693,28 +791,28 @@
   SSL_load_error_strings();
   SSL_library_init();
   ctx = NULL;
-  if (0 == strcmp ("sslv23", protocol))
+  if (0 == strcmp ("sslv23", g_protocol))
     {
       verb ("V: using SSLv23_client_method()\n");
       ctx = SSL_CTX_new (SSLv23_client_method());
     }
-  else if (0 == strcmp ("sslv3", protocol))
+  else if (0 == strcmp ("sslv3", g_protocol))
     {
       verb ("V: using SSLv3_client_method()\n");
       ctx = SSL_CTX_new (SSLv3_client_method());
     }
-  else if (0 == strcmp ("tlsv1", protocol))
+  else if (0 == strcmp ("tlsv1", g_protocol))
     {
       verb ("V: using TLSv1_client_method()\n");
       ctx = SSL_CTX_new (TLSv1_client_method());
     }
   else
-    die ("Unsupported protocol `%s'\n", protocol);
+    die ("Unsupported protocol `%s'\n", g_protocol);
   if (ctx == NULL)
-    die ("OpenSSL failed to support protocol `%s'\n", protocol);
-  if (ca_racket)
+    die ("OpenSSL failed to support protocol `%s'\n", g_protocol);
+  if (g_ca_racket)
     {
-      if (-1 == stat (ca_cert_container, &statbuf))
+      if (-1 == stat (g_ca_cert_container, &statbuf))
         {
           die ("Unable to stat CA certficate container\n");
         }
@@ -723,19 +821,22 @@
           switch (statbuf.st_mode & S_IFMT)
             {
             case S_IFREG:
-              if (1 != SSL_CTX_load_verify_locations (ctx, ca_cert_container, NULL))
-                fprintf (stderr, "SSL_CTX_load_verify_locations failed\n");
+              if (1 != SSL_CTX_load_verify_locations(
+                           ctx, g_ca_cert_container, NULL))
+                fprintf(stderr, "SSL_CTX_load_verify_locations failed\n");
               break;
             case S_IFDIR:
-              if (1 != SSL_CTX_load_verify_locations (ctx, NULL, ca_cert_container))
-                fprintf (stderr, "SSL_CTX_load_verify_locations failed\n");
+              if (1 != SSL_CTX_load_verify_locations(
+                           ctx, NULL, g_ca_cert_container))
+                fprintf(stderr, "SSL_CTX_load_verify_locations failed\n");
               break;
             default:
               die ("Unable to load CA certficate container\n");
             }
         }
     }
-  if (NULL == (s_bio = make_ssl_bio (ctx)))
+  verb ("V: setting up connection to %s:%s\n", g_host, g_port);
+  if (NULL == (s_bio = make_ssl_bio(ctx, g_host, g_port, g_proxy)))
     die ("SSL BIO setup failed\n");
   BIO_get_ssl (s_bio, &ssl);
   if (NULL == ssl)
@@ -745,11 +846,7 @@
       SSL_set_info_callback (ssl, openssl_time_callback);
     }
   SSL_set_mode (ssl, SSL_MODE_AUTO_RETRY);
-  SSL_set_tlsext_host_name (ssl, host);
-  verb ("V: opening socket to %s:%s\n", host, port);
-  if ( (1 != BIO_set_conn_hostname (s_bio, host)) ||
-       (1 != BIO_set_conn_port (s_bio, port)))
-    die ("Failed to initialize connection to `%s:%s'\n", host, port);
+  SSL_set_tlsext_host_name (ssl, g_host);
   if (NULL == BIO_new_fp (stdout, BIO_NOCLOSE))
     die ("BIO_new_fp returned error, possibly: %s", strerror (errno));
   // This should run in seccomp
@@ -759,9 +856,9 @@
   if (1 != BIO_do_handshake (s_bio))
     die ("SSL handshake failed\n");
   // Verify the peer certificate against the CA certs on the local system
-  if (ca_racket)
+  if (g_ca_racket)
     {
-      inspect_key (ssl, hostname_to_verify);
+      inspect_key (ssl, g_host);
     }
   else
     {
@@ -793,19 +890,19 @@
   int leap;
   if (argc != 12)
     return 1;
-  host = argv[1];
-  hostname_to_verify = argv[1];
-  port = argv[2];
-  protocol = argv[3];
-  ca_cert_container = argv[6];
-  ca_racket = (0 != strcmp ("unchecked", argv[4]));
+  g_host = argv[1];
+  g_port = argv[2];
+  validate_port(g_port);
+  g_protocol = argv[3];
+  g_ca_cert_container = argv[6];
+  g_ca_racket = (0 != strcmp ("unchecked", argv[4]));
   verbose = (0 != strcmp ("quiet", argv[5]));
   setclock = (0 == strcmp ("setclock", argv[7]));
   showtime = (0 == strcmp ("showtime", argv[8]));
   showtime_raw = (0 == strcmp ("showtime=raw", argv[8]));
   timewarp = (0 == strcmp ("timewarp", argv[9]));
   leap = (0 == strcmp ("leapaway", argv[10]));
-  proxy = (0 == strcmp ("none", argv[11]) ? NULL : argv[11]);
+  g_proxy = (0 == strcmp ("none", argv[11]) ? NULL : argv[11]);
   clock_init_time (&warp_time, RECENT_COMPILE_DATE, 0);
   if (timewarp)
     {
diff --git a/src/tlsdate-helper.h b/src/tlsdate-helper.h
index 2b52a95..0e89b9a 100644
--- a/src/tlsdate-helper.h
+++ b/src/tlsdate-helper.h
@@ -79,19 +79,6 @@
 // To support our RFC 2595 wildcard verification
 #define RFC2595_MIN_LABEL_COUNT 3
 
-static int ca_racket;
-
-static const char *host;
-
-static const char *hostname_to_verify;
-
-static const char *port;
-
-static const char *protocol;
-
-static char *proxy;
-
-static const char *ca_cert_container;
 void openssl_time_callback (const SSL* ssl, int where, int ret);
 uint32_t get_certificate_keybits (EVP_PKEY *public_key);
 uint32_t check_cn (SSL *ssl, const char *hostname);