BIO_s_connect(): Enable BIO_gets()
authorDr. David von Oheimb <David.von.Oheimb@siemens.com>
Thu, 8 Jul 2021 22:31:21 +0000 (00:31 +0200)
committerTomas Mraz <tomas@openssl.org>
Mon, 15 Nov 2021 13:40:16 +0000 (14:40 +0100)
Fixes #16028

Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/16030)

crypto/bio/bss_conn.c
doc/man3/BIO_s_connect.pod
test/http_test.c

index d146c97b82a5c17aee9f64957e00392db87c6210..8bc53548ca0b9edf049aeb81eb36af125d9e425e 100644 (file)
@@ -42,6 +42,7 @@ typedef struct bio_connect_st {
 static int conn_write(BIO *h, const char *buf, int num);
 static int conn_read(BIO *h, char *buf, int size);
 static int conn_puts(BIO *h, const char *str);
+static int conn_gets(BIO *h, char *buf, int size);
 static long conn_ctrl(BIO *h, int cmd, long arg1, void *arg2);
 static int conn_new(BIO *h);
 static int conn_free(BIO *data);
@@ -68,7 +69,7 @@ static const BIO_METHOD methods_connectp = {
     bread_conv,
     conn_read,
     conn_puts,
-    NULL,                       /* conn_gets, */
+    conn_gets,
     conn_ctrl,
     conn_new,
     conn_free,
@@ -595,6 +596,56 @@ static int conn_puts(BIO *bp, const char *str)
     return ret;
 }
 
+int conn_gets(BIO *bio, char *buf, int size)
+{
+    BIO_CONNECT *data;
+    char *ptr = buf;
+    int ret = 0;
+
+    if (buf == NULL) {
+        ERR_raise(ERR_LIB_BIO, ERR_R_PASSED_NULL_PARAMETER);
+        return -1;
+    }
+    if (size <= 0) {
+        ERR_raise(ERR_LIB_BIO, BIO_R_INVALID_ARGUMENT);
+        return -1;
+    }
+    *buf = '\0';
+
+    if (bio == NULL || bio->ptr == NULL) {
+        ERR_raise(ERR_LIB_BIO, ERR_R_PASSED_NULL_PARAMETER);
+        return -1;
+    }
+    data = (BIO_CONNECT *)bio->ptr;
+    if (data->state != BIO_CONN_S_OK) {
+        ret = conn_state(bio, data);
+        if (ret <= 0)
+            return ret;
+    }
+
+    clear_socket_error();
+    while (size-- > 1) {
+# ifndef OPENSSL_NO_KTLS
+        if (BIO_get_ktls_recv(bio))
+            ret = ktls_read_record(bio->num, ptr, 1);
+        else
+# endif
+            ret = readsocket(bio->num, ptr, 1);
+        BIO_clear_retry_flags(bio);
+        if (ret <= 0) {
+            if (BIO_sock_should_retry(ret))
+                BIO_set_retry_read(bio);
+            else if (ret == 0)
+                bio->flags |= BIO_FLAGS_IN_EOF;
+            break;
+        }
+        if (*ptr++ == '\n')
+            break;
+    }
+    *ptr = '\0';
+    return ret > 0 || (bio->flags & BIO_FLAGS_IN_EOF) != 0 ? ptr - buf : ret;
+}
+
 BIO *BIO_new_connect(const char *str)
 {
     BIO *ret;
index 88450dffce527e1efd72bb997ebe9f3341242102..782e076a94dcaa1c7cdb7aa1ac71d0b319bcd185 100644 (file)
@@ -44,7 +44,7 @@ on the underlying connection. If no connection is established
 and the port and hostname (see below) is set up properly then
 a connection is established first.
 
-Connect BIOs support BIO_puts() but not BIO_gets().
+Connect BIOs support BIO_puts() and BIO_gets().
 
 If the close flag is set on a connect BIO then any active
 connection is shutdown and the socket closed when the BIO
@@ -199,6 +199,8 @@ BIO_set_conn_int_port(), BIO_get_conn_int_port(), BIO_set_conn_ip(), and BIO_get
 were removed in OpenSSL 1.1.0.
 Use BIO_set_conn_address() and BIO_get_conn_address() instead.
 
+Connect BIOs support BIO_gets() since OpenSSL 3.1.
+
 =head1 COPYRIGHT
 
 Copyright 2000-2021 The OpenSSL Project Authors. All Rights Reserved.
index b9f7452744cb92bb6f4f1f82527dfd1ff2d0dcd9..edf995eb033d13cf3ae379d8c4e37661589ae432 100644 (file)
@@ -21,17 +21,21 @@ static X509 *x509 = NULL;
 
 typedef struct {
     BIO *out;
+    const char *content_type;
+    const char *txt;
     char version;
     int keep_alive;
 } server_args;
 
 /*-
  * Pretty trivial HTTP mock server:
- * For POST, copy request headers+body from mem BIO 'in' as response to 'out'.
- * For GET, redirect to RPATH, else respond with 'rsp' of ASN1 type 'it'.
- * Respond with HTTP version 1.'version' and 'keep_alive' (unless implicit).
+ * For POST, copy request headers+body from mem BIO |in| as response to |out|.
+ * For GET, redirect to RPATH unless already there, else use |content_type| and
+ * respond with |txt| if not NULL, else with |rsp| of ASN1 type |it|.
+ * Response hdr has HTTP version 1.|version| and |keep_alive| (unless implicit).
  */
 static int mock_http_server(BIO *in, BIO *out, char version, int keep_alive,
+                            const char *content_type, const char *txt,
                             ASN1_VALUE *rsp, const ASN1_ITEM *it)
 {
     const char *req, *path;
@@ -40,7 +44,7 @@ static int mock_http_server(BIO *in, BIO *out, char version, int keep_alive,
     int is_get = count >= 4 && strncmp(hdr, "GET ", 4) == 0;
     int len;
 
-    /* first line should contain "<GET or POST> <path> HTTP/1.x" */
+    /* first line should contain "(GET|POST) <path> HTTP/1.x" */
     if (is_get)
         hdr += 4;
     else if (TEST_true(count >= 5 && strncmp(hdr, "POST ", 5) == 0))
@@ -79,11 +83,15 @@ static int mock_http_server(BIO *in, BIO *out, char version, int keep_alive,
                        version == '0' ? "keep-alive" : "close") <= 0)
             return 0;
     if (is_get) { /* construct new header and body */
-        if ((len = ASN1_item_i2d(rsp, NULL, it)) <= 0)
+        if (txt != NULL)
+            len = strlen(txt);
+        else if ((len = ASN1_item_i2d(rsp, NULL, it)) <= 0)
             return 0;
-        if (BIO_printf(out, "Content-Type: application/x-x509-ca-cert\r\n"
-                       "Content-Length: %d\r\n\r\n", len) <= 0)
+        if (BIO_printf(out, "Content-Type: %s\r\n"
+                       "Content-Length: %d\r\n\r\n", content_type, len) <= 0)
             return 0;
+        if (txt != NULL)
+            return BIO_puts(out, txt);
         return ASN1_item_i2d_bio(it, out, rsp);
     } else {
         len = strlen("Connection: ");
@@ -106,47 +114,91 @@ static long http_bio_cb_ex(BIO *bio, int oper, const char *argp, size_t len,
 
     if (oper == (BIO_CB_CTRL | BIO_CB_RETURN) && cmd == BIO_CTRL_FLUSH)
         ret = mock_http_server(bio, args->out, args->version, args->keep_alive,
+                               args->content_type, args->txt,
                                (ASN1_VALUE *)x509, x509_it);
     return ret;
 }
 
-static int test_http_x509(int do_get)
+#define text1 "test\n"
+#define text2 "more\n"
+#define REAL_SERVER_URL "http://httpbin.org/"
+#define DOCTYPE_HTML "<!DOCTYPE html>\n"
+
+static int test_http_method(int do_get, int do_txt)
 {
-    X509 *rcert = NULL;
     BIO *wbio = BIO_new(BIO_s_mem());
     BIO *rbio = BIO_new(BIO_s_mem());
-    server_args mock_args = { NULL, '0', 0 };
-    BIO *rsp, *req = ASN1_item_i2d_mem_bio(x509_it, (ASN1_VALUE *)x509);
+    server_args mock_args = { NULL, NULL, NULL, '0', 0 };
+    BIO *req, *rsp;
     STACK_OF(CONF_VALUE) *headers = NULL;
-    const char content_type[] = "application/x-x509-ca-cert";
+    const char *content_type;
     int res = 0;
-
+    int real_server = do_txt && 0; /* remove "&& 0" for using real server */
+
+    if (do_txt) {
+        content_type = "text/plain";
+        req = BIO_new(BIO_s_mem());
+        if (req == NULL
+                || BIO_puts(req, text1) != sizeof(text1) - 1
+                || BIO_puts(req, text2) != sizeof(text2) - 1) {
+            BIO_free(req);
+            req = NULL;
+        }
+        mock_args.txt = text1;
+    } else {
+        content_type = "application/x-x509-ca-cert";
+        req = ASN1_item_i2d_mem_bio(x509_it, (ASN1_VALUE *)x509);
+        mock_args.txt = NULL;
+    }
     if (wbio == NULL || rbio == NULL || req == NULL)
         goto err;
+
     mock_args.out = rbio;
+    mock_args.content_type = content_type;
     BIO_set_callback_ex(wbio, http_bio_cb_ex);
     BIO_set_callback_arg(wbio, (char *)&mock_args);
 
     rsp = do_get ?
-        OSSL_HTTP_get("/will-be-redirected",
+        OSSL_HTTP_get(real_server ? REAL_SERVER_URL :
+                      do_txt ? RPATH : "/will-be-redirected",
                       NULL /* proxy */, NULL /* no_proxy */,
-                      wbio, rbio, NULL /* bio_update_fn */, NULL /* arg */,
-                      0 /* buf_size */, headers, content_type,
-                      1 /* expect_asn1 */,
+                      real_server ? NULL : wbio,
+                      real_server ? NULL : rbio,
+                      NULL /* bio_update_fn */, NULL /* arg */,
+                      0 /* buf_size */, headers,
+                      real_server ? "text/html; charset=utf-8":  content_type,
+                      !do_txt /* expect_asn1 */,
                       OSSL_HTTP_DEFAULT_MAX_RESP_LEN, 0 /* timeout */)
         : OSSL_HTTP_transfer(NULL, NULL /* host */, NULL /* port */, RPATH,
                              0 /* use_ssl */,NULL /* proxy */, NULL /* no_pr */,
                              wbio, rbio, NULL /* bio_fn */, NULL /* arg */,
                              0 /* buf_size */, headers, content_type,
-                             req, content_type, 1 /* expect_asn1 */,
+                             req, content_type, !do_txt /* expect_asn1 */,
                              OSSL_HTTP_DEFAULT_MAX_RESP_LEN, 0 /* timeout */,
                              0 /* keep_alive */);
-    rcert = d2i_X509_bio(rsp, NULL);
-    BIO_free(rsp);
-    res = TEST_ptr(rcert) && TEST_int_eq(X509_cmp(x509, rcert), 0);
+    if (rsp != NULL) {
+        if (do_get && real_server) {
+            char rtext[sizeof(DOCTYPE_HTML)];
+
+            res = TEST_int_eq(BIO_gets(rsp, rtext, sizeof(rtext)),
+                              sizeof(DOCTYPE_HTML) - 1)
+                && TEST_str_eq(rtext, DOCTYPE_HTML);
+        } else if (do_txt) {
+            char rtext[sizeof(text1) + 1 /* more space than needed */];
+
+            res = TEST_int_eq(BIO_gets(rsp, rtext, sizeof(rtext)),
+                              sizeof(text1) - 1)
+                && TEST_str_eq(rtext, text1);
+        } else {
+            X509 *rcert = d2i_X509_bio(rsp, NULL);
+
+            res = TEST_ptr(rcert) && TEST_int_eq(X509_cmp(x509, rcert), 0);
+            X509_free(rcert);
+        }
+        BIO_free(rsp);
+    }
 
  err:
-    X509_free(rcert);
     BIO_free(req);
     BIO_free(wbio);
     BIO_free(rbio);
@@ -159,8 +211,8 @@ static int test_http_keep_alive(char version, int keep_alive, int kept_alive)
     BIO *wbio = BIO_new(BIO_s_mem());
     BIO *rbio = BIO_new(BIO_s_mem());
     BIO *rsp;
-    server_args mock_args = { NULL, '0', 0 };
     const char *const content_type = "application/x-x509-ca-cert";
+    server_args mock_args = { NULL, content_type, NULL, '0', 0 };
     OSSL_HTTP_REQ_CTX *rctx = NULL;
     int i, res = 0;
 
@@ -306,14 +358,24 @@ static int test_http_url_invalid_path(void)
     return test_http_url_invalid("https://[FF01::101]pkix");
 }
 
+static int test_http_get_txt(void)
+{
+    return test_http_method(1 /* GET */, 1);
+}
+
+static int test_http_post_txt(void)
+{
+    return test_http_method(0 /* POST */, 1);
+}
+
 static int test_http_get_x509(void)
 {
-    return test_http_x509(1);
+    return test_http_method(1 /* GET */, 0); /* includes redirection */
 }
 
 static int test_http_post_x509(void)
 {
-    return test_http_x509(0);
+    return test_http_method(0 /* POST */, 0);
 }
 
 static int test_http_keep_alive_0_no_no(void)
@@ -380,6 +442,8 @@ int setup_tests(void)
     ADD_TEST(test_http_url_invalid_prefix);
     ADD_TEST(test_http_url_invalid_port);
     ADD_TEST(test_http_url_invalid_path);
+    ADD_TEST(test_http_get_txt);
+    ADD_TEST(test_http_post_txt);
     ADD_TEST(test_http_get_x509);
     ADD_TEST(test_http_post_x509);
     ADD_TEST(test_http_keep_alive_0_no_no);