Better handle IPv6.

  ∙ host:port parsing, where unavoidable, is now IPv6-friendly.
  ∙ |BIO_C_GET_CONNECT| is simply removed.
  ∙ bssl -accept now listens on both IPv6 and IPv4.

Change-Id: I1cbd8a79c0199bab3ced4c4fd79d2cc5240f250c
Reviewed-on: https://boringssl-review.googlesource.com/6214
Reviewed-by: Adam Langley <alangley@gmail.com>
diff --git a/crypto/bio/connect.c b/crypto/bio/connect.c
index cc28ef8..0b34d7f 100644
--- a/crypto/bio/connect.c
+++ b/crypto/bio/connect.c
@@ -93,7 +93,6 @@
   char *param_port;
   int nbio;
 
-  uint8_t ip[4];
   unsigned short port;
 
   struct sockaddr_storage them;
@@ -114,23 +113,59 @@
 }
 #endif
 
-/* maybe_copy_ipv4_address sets |*ipv4| to the IPv4 address from |ss| (in
- * big-endian order), if |ss| contains an IPv4 socket address. */
-static void maybe_copy_ipv4_address(uint8_t *ipv4,
-                                    const struct sockaddr_storage *ss) {
-  const struct sockaddr_in *sin;
+/* split_host_and_port sets |*out_host| and |*out_port| to the host and port
+ * parsed from |name|. It returns one on success or zero on error. Even when
+ * successful, |*out_port| may be NULL on return if no port was specified. */
+static int split_host_and_port(char **out_host, char **out_port, const char *name) {
+  const char *host, *port = NULL;
+  size_t host_len = 0;
 
-  if (ss->ss_family != AF_INET) {
-    return;
+  *out_host = NULL;
+  *out_port = NULL;
+
+  if (name[0] == '[') {  /* bracketed IPv6 address */
+    const char *close = strchr(name, ']');
+    if (close == NULL) {
+      return 0;
+    }
+    host = name + 1;
+    host_len = close - host;
+    if (close[1] == ':') {  /* [IP]:port */
+      port = close + 2;
+    } else if (close[1] != 0) {
+      return 0;
+    }
+  } else {
+    const char *colon = strchr(name, ':');
+    if (colon == NULL || strchr(colon + 1, ':') != NULL) {  /* IPv6 address */
+      host = name;
+      host_len = strlen(name);
+    } else {  /* host:port */
+      host = name;
+      host_len = colon - name;
+      port = colon + 1;
+    }
   }
 
-  sin = (const struct sockaddr_in*) ss;
-  memcpy(ipv4, &sin->sin_addr, 4);
+  *out_host = BUF_strndup(host, host_len);
+  if (*out_host == NULL) {
+    return 0;
+  }
+  if (port == NULL) {
+    *out_port = NULL;
+    return 1;
+  }
+  *out_port = OPENSSL_strdup(port);
+  if (*out_port == NULL) {
+    OPENSSL_free(*out_host);
+    *out_host = NULL;
+    return 0;
+  }
+  return 1;
 }
 
 static int conn_state(BIO *bio, BIO_CONNECT *c) {
   int ret = -1, i;
-  char *p, *q;
   int (*cb)(const BIO *, int, int) = NULL;
 
   if (c->info_callback != NULL) {
@@ -140,36 +175,30 @@
   for (;;) {
     switch (c->state) {
       case BIO_CONN_S_BEFORE:
-        p = c->param_hostname;
-        if (p == NULL) {
+        /* If there's a hostname and a port, assume that both are
+         * exactly what they say. If there is only a hostname, try
+         * (just once) to split it into a hostname and port. */
+
+        if (c->param_hostname == NULL) {
           OPENSSL_PUT_ERROR(BIO, BIO_R_NO_HOSTNAME_SPECIFIED);
           goto exit_loop;
         }
-        for (; *p != 0; p++) {
-          if (*p == ':' || *p == '/') {
-            break;
-          }
-        }
-
-        i = *p;
-        if (i == ':' || i == '/') {
-          *(p++) = 0;
-          if (i == ':') {
-            for (q = p; *q; q++) {
-              if (*q == '/') {
-                *q = 0;
-                break;
-              }
-            }
-            OPENSSL_free(c->param_port);
-            c->param_port = BUF_strdup(p);
-          }
-        }
 
         if (c->param_port == NULL) {
-          OPENSSL_PUT_ERROR(BIO, BIO_R_NO_PORT_SPECIFIED);
-          ERR_add_error_data(2, "host=", c->param_hostname);
-          goto exit_loop;
+          char *host, *port;
+          if (!split_host_and_port(&host, &port, c->param_hostname) ||
+              port == NULL) {
+            OPENSSL_free(host);
+            OPENSSL_free(port);
+            OPENSSL_PUT_ERROR(BIO, BIO_R_NO_PORT_SPECIFIED);
+            ERR_add_error_data(2, "host=", c->param_hostname);
+            goto exit_loop;
+          }
+
+          OPENSSL_free(c->param_port);
+          c->param_port = port;
+          OPENSSL_free(c->param_hostname);
+          c->param_hostname = host;
         }
 
         if (!bio_ip_and_port_to_socket_and_addr(
@@ -180,9 +209,6 @@
           goto exit_loop;
         }
 
-        memset(c->ip, 0, 4);
-        maybe_copy_ipv4_address(c->ip, &c->them);
-
         if (c->nbio) {
           if (!bio_socket_nbio(bio->num, 1)) {
             OPENSSL_PUT_ERROR(BIO, BIO_R_ERROR_SETTING_NBIO);
@@ -376,7 +402,6 @@
 
 static long conn_ctrl(BIO *bio, int cmd, long num, void *ptr) {
   int *ip;
-  const char **pptr;
   long ret = 1;
   BIO_CONNECT *data;
 
@@ -397,25 +422,6 @@
         ret = 1;
       }
       break;
-    case BIO_C_GET_CONNECT:
-      /* TODO(fork): can this be removed? (Or maybe this whole file). */
-      if (ptr != NULL) {
-        pptr = (const char **)ptr;
-        if (num == 0) {
-          *pptr = data->param_hostname;
-        } else if (num == 1) {
-          *pptr = data->param_port;
-        } else if (num == 2) {
-          *pptr = (char *) &data->ip[0];
-        } else if (num == 3) {
-          *((int *)ptr) = data->port;
-        }
-        if (!bio->init) {
-          *pptr = "not initialized";
-        }
-        ret = 1;
-      }
-      break;
     case BIO_C_SET_CONNECT:
       if (ptr != NULL) {
         bio->init = 1;
diff --git a/include/openssl/bio.h b/include/openssl/bio.h
index 481b97e..44de50f 100644
--- a/include/openssl/bio.h
+++ b/include/openssl/bio.h
@@ -842,7 +842,6 @@
 #define BIO_C_GET_MD_CTX			120
 #define BIO_C_GET_PROXY_PARAM			121
 #define BIO_C_SET_BUFF_READ_DATA		122 /* data to read first */
-#define BIO_C_GET_CONNECT			123
 #define BIO_C_GET_ACCEPT			124
 #define BIO_C_SET_SSL_RENEGOTIATE_BYTES		125
 #define BIO_C_GET_SSL_NUM_RENEGOTIATES		126
diff --git a/tool/transport_common.cc b/tool/transport_common.cc
index 98f0f95..cfda6c3 100644
--- a/tool/transport_common.cc
+++ b/tool/transport_common.cc
@@ -133,19 +133,19 @@
 }
 
 bool Accept(int *out_sock, const std::string &port) {
-  struct sockaddr_in addr, cli_addr;
+  struct sockaddr_in6 addr, cli_addr;
   socklen_t cli_addr_len = sizeof(cli_addr);
   memset(&addr, 0, sizeof(addr));
 
-  addr.sin_family = AF_INET;
-  addr.sin_addr.s_addr = INADDR_ANY;
-  addr.sin_port = htons(atoi(port.c_str()));
+  addr.sin6_family = AF_INET6;
+  addr.sin6_addr = in6addr_any;
+  addr.sin6_port = htons(atoi(port.c_str()));
 
   bool ok = false;
   int server_sock = -1;
 
   server_sock =
-      socket(addr.sin_family, SOCK_STREAM, 0);
+      socket(addr.sin6_family, SOCK_STREAM, 0);
   if (server_sock < 0) {
     perror("socket");
     goto out;