Add a simplified SSL BIO for curl.

A recent change to curl[1] added support for HTTPS proxies, which
involves running a TLS connection inside another TLS connection. This
was done by using SSL BIOs, which we removed from BoringSSL for being
crazy.

This change adds a stripped-down version of the SSL BIO to decrepit in
order to suport curl.

[1] https://github.com/curl/curl/commit/cb4e2be7c6d42ca0780f8e0a747cecf9ba45f151

Change-Id: I9cb8f2db5b28a5a70724f6f93544297c380ac124
Reviewed-on: https://boringssl-review.googlesource.com/12631
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/bio/bio.c b/crypto/bio/bio.c
index 675e903..9619c22 100644
--- a/crypto/bio/bio.c
+++ b/crypto/bio/bio.c
@@ -604,3 +604,7 @@
 
   return 1;
 }
+
+void BIO_set_retry_special(BIO *bio) {
+  bio->flags |= BIO_FLAGS_READ | BIO_FLAGS_IO_SPECIAL;
+}
diff --git a/decrepit/CMakeLists.txt b/decrepit/CMakeLists.txt
index 6a5462c..1e2386a 100644
--- a/decrepit/CMakeLists.txt
+++ b/decrepit/CMakeLists.txt
@@ -1,4 +1,5 @@
 add_subdirectory(bio)
+add_subdirectory(biossl)
 add_subdirectory(blowfish)
 add_subdirectory(cast)
 add_subdirectory(des)
@@ -17,6 +18,7 @@
   decrepit
 
   $<TARGET_OBJECTS:bio_decrepit>
+  $<TARGET_OBJECTS:biossl_decrepit>
   $<TARGET_OBJECTS:blowfish>
   $<TARGET_OBJECTS:cast>
   $<TARGET_OBJECTS:des_decrepit>
diff --git a/decrepit/biossl/CMakeLists.txt b/decrepit/biossl/CMakeLists.txt
new file mode 100644
index 0000000..39fe139
--- /dev/null
+++ b/decrepit/biossl/CMakeLists.txt
@@ -0,0 +1,9 @@
+include_directories(../../include)
+
+add_library(
+  biossl_decrepit
+
+  OBJECT
+
+  bio_ssl.c
+)
diff --git a/decrepit/biossl/bio_ssl.c b/decrepit/biossl/bio_ssl.c
new file mode 100644
index 0000000..4574c31
--- /dev/null
+++ b/decrepit/biossl/bio_ssl.c
@@ -0,0 +1,185 @@
+/*
+ * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the OpenSSL license (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <openssl/ssl.h>
+
+#include <openssl/bio.h>
+
+
+static int ssl_read(BIO *bio, char *out, int outl) {
+  SSL *ssl = bio->ptr;
+  if (ssl == NULL) {
+    return 0;
+  }
+
+  BIO_clear_retry_flags(bio);
+
+  const int ret = SSL_read(ssl, out, outl);
+
+  switch (SSL_get_error(ssl, ret)) {
+    case SSL_ERROR_WANT_READ:
+      BIO_set_retry_read(bio);
+      break;
+
+    case SSL_ERROR_WANT_WRITE:
+      BIO_set_retry_write(bio);
+      break;
+
+    case SSL_ERROR_WANT_X509_LOOKUP:
+      BIO_set_retry_special(bio);
+      bio->retry_reason = BIO_RR_SSL_X509_LOOKUP;
+      break;
+
+    case SSL_ERROR_WANT_ACCEPT:
+      BIO_set_retry_special(bio);
+      bio->retry_reason = BIO_RR_ACCEPT;
+      break;
+
+    case SSL_ERROR_WANT_CONNECT:
+      BIO_set_retry_special(bio);
+      bio->retry_reason = BIO_RR_CONNECT;
+      break;
+
+    case SSL_ERROR_NONE:
+    case SSL_ERROR_SYSCALL:
+    case SSL_ERROR_SSL:
+    case SSL_ERROR_ZERO_RETURN:
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+static int ssl_write(BIO *bio, const char *out, int outl) {
+  SSL *ssl = bio->ptr;
+  if (ssl == NULL) {
+    return 0;
+  }
+
+  BIO_clear_retry_flags(bio);
+
+  const int ret = SSL_write(ssl, out, outl);
+
+  switch (SSL_get_error(ssl, ret)) {
+    case SSL_ERROR_WANT_WRITE:
+      BIO_set_retry_write(bio);
+      break;
+
+    case SSL_ERROR_WANT_READ:
+      BIO_set_retry_read(bio);
+      break;
+
+    case SSL_ERROR_WANT_X509_LOOKUP:
+      BIO_set_retry_special(bio);
+      bio->retry_reason = BIO_RR_SSL_X509_LOOKUP;
+      break;
+
+    case SSL_ERROR_WANT_CONNECT:
+      BIO_set_retry_special(bio);
+      bio->retry_reason = BIO_RR_CONNECT;
+      break;
+
+    case SSL_ERROR_NONE:
+    case SSL_ERROR_SYSCALL:
+    case SSL_ERROR_SSL:
+    default:
+      break;
+  }
+
+  return ret;
+}
+
+static long ssl_ctrl(BIO *bio, int cmd, long num, void *ptr) {
+  SSL *ssl = bio->ptr;
+  if (ssl == NULL && cmd != BIO_C_SET_SSL) {
+    return 0;
+  }
+
+  switch (cmd) {
+    case BIO_C_SET_SSL:
+      bio->shutdown = num;
+      bio->ptr = ptr;
+      bio->init = 1;
+      return 1;
+
+    case BIO_CTRL_GET_CLOSE:
+      return bio->shutdown;
+
+    case BIO_CTRL_SET_CLOSE:
+      bio->shutdown = num;
+      return 1;
+
+    case BIO_CTRL_WPENDING:
+      return BIO_ctrl(ssl->wbio, cmd, num, ptr);
+
+    case BIO_CTRL_PENDING:
+      return SSL_pending(ssl);
+
+    case BIO_CTRL_FLUSH: {
+      BIO_clear_retry_flags(bio);
+      long ret = BIO_ctrl(ssl->wbio, cmd, num, ptr);
+      BIO_copy_next_retry(bio);
+      return ret;
+    }
+
+    case BIO_CTRL_PUSH:
+    case BIO_CTRL_POP:
+    case BIO_CTRL_DUP:
+      return -1;
+
+    default:
+      return BIO_ctrl(ssl->rbio, cmd, num, ptr);
+  }
+}
+
+static int ssl_new(BIO *bio) {
+  return 1;
+}
+
+static int ssl_free(BIO *bio) {
+  SSL *ssl = bio->ptr;
+
+  if (ssl == NULL) {
+    return 1;
+  }
+
+  SSL_shutdown(ssl);
+  if (bio->shutdown) {
+    SSL_free(ssl);
+  }
+
+  return 1;
+}
+
+static long ssl_callback_ctrl(BIO *bio, int cmd, bio_info_cb fp) {
+  SSL *ssl = bio->ptr;
+  if (ssl == NULL) {
+    return 0;
+  }
+
+  switch (cmd) {
+    case BIO_CTRL_SET_CALLBACK:
+      return -1;
+
+    default:
+      return BIO_callback_ctrl(ssl->rbio, cmd, fp);
+  }
+}
+
+static const BIO_METHOD ssl_method = {
+    BIO_TYPE_SSL, "SSL",    ssl_write, ssl_read, NULL,
+    NULL,         ssl_ctrl, ssl_new,   ssl_free, ssl_callback_ctrl,
+};
+
+const BIO_METHOD *BIO_f_ssl(void) { return &ssl_method; }
+
+long BIO_set_ssl(BIO *bio, SSL *ssl, int take_owership) {
+  return BIO_ctrl(bio, BIO_C_SET_SSL, take_owership, ssl);
+}
diff --git a/include/openssl/bio.h b/include/openssl/bio.h
index 58a4747..ef7e465 100644
--- a/include/openssl/bio.h
+++ b/include/openssl/bio.h
@@ -679,6 +679,8 @@
  * on one line. */
 OPENSSL_EXPORT const BIO_METHOD *BIO_f_base64(void);
 
+OPENSSL_EXPORT void BIO_set_retry_special(BIO *bio);
+
 
 /* Private functions */
 
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 8b443fd..5338037 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -3639,6 +3639,17 @@
 /* SSL_enable_tls_channel_id calls |SSL_set_tls_channel_id_enabled|. */
 OPENSSL_EXPORT int SSL_enable_tls_channel_id(SSL *ssl);
 
+/* BIO_f_ssl returns a |BIO_METHOD| that can wrap an |SSL*| in a |BIO*|. Note
+ * that this has quite different behaviour from the version in OpenSSL (notably
+ * that it doesn't try to auto renegotiate). */
+OPENSSL_EXPORT const BIO_METHOD *BIO_f_ssl(void);
+
+/* BIO_set_ssl sets |ssl| as the underlying connection for |bio|, which must
+ * have been created using |BIO_f_ssl|. If |take_owership| is true, |bio| will
+ * call |SSL_free| on |ssl| when closed. It returns one on success or something
+ * other than one on error. */
+OPENSSL_EXPORT long BIO_set_ssl(BIO *bio, SSL *ssl, int take_owership);
+
 
 /* Private structures.
  *