Improve BIO_socket_wait(), BIO_wait(), BIO_connect_retry(), and their docs
[openssl.git] / crypto / bio / bio_lib.c
index 67acac3d28a27591cb0a11f26988ede145627663..c3c798d4b4fd18542e8f9c55b7fe8603790d5eca 100644 (file)
@@ -1,7 +1,7 @@
 /*
- * Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 1995-2020 The OpenSSL Project Authors. All Rights Reserved.
  *
- * Licensed under the OpenSSL license (the "License").  You may not use
+ * Licensed under the Apache License 2.0 (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
@@ -10,7 +10,7 @@
 #include <stdio.h>
 #include <errno.h>
 #include <openssl/crypto.h>
-#include "bio_lcl.h"
+#include "bio_local.h"
 #include "internal/cryptlib.h"
 
 
@@ -34,9 +34,8 @@ static long bio_call_callback(BIO *b, int oper, const char *argp, size_t len,
     long ret;
     int bareoper;
 
-    if (b->callback_ex != NULL) {
+    if (b->callback_ex != NULL)
         return b->callback_ex(b, oper, argp, len, argi, argl, inret, processed);
-    }
 
     /* Strip off any BIO_CB_RETURN flag */
     bareoper = oper & ~BIO_CB_RETURN;
@@ -51,17 +50,17 @@ static long bio_call_callback(BIO *b, int oper, const char *argp, size_t len,
             return -1;
 
         argi = (int)len;
+    }
 
-        if (inret && (oper & BIO_CB_RETURN)) {
-            if (*processed > INT_MAX)
-                return -1;
-            inret = *processed;
-        }
+    if (inret > 0 && (oper & BIO_CB_RETURN) && bareoper != BIO_CB_CTRL) {
+        if (*processed > INT_MAX)
+            return -1;
+        inret = *processed;
     }
 
     ret = b->callback(b, oper, argp, argi, argl, inret);
 
-    if (ret >= 0 && (HAS_LEN_OPER(bareoper) || bareoper == BIO_CB_PUTS)) {
+    if (ret > 0 && (oper & BIO_CB_RETURN) && bareoper != BIO_CB_CTRL) {
         *processed = (size_t)ret;
         ret = 1;
     }
@@ -75,7 +74,7 @@ BIO *BIO_new(const BIO_METHOD *method)
 
     if (bio == NULL) {
         BIOerr(BIO_F_BIO_NEW, ERR_R_MALLOC_FAILURE);
-        return (NULL);
+        return NULL;
     }
 
     bio->method = method;
@@ -98,6 +97,8 @@ BIO *BIO_new(const BIO_METHOD *method)
         CRYPTO_THREAD_lock_free(bio->lock);
         goto err;
     }
+    if (method->create == NULL)
+        bio->init = 1;
 
     return bio;
 
@@ -258,7 +259,7 @@ static int bio_read_intern(BIO *b, void *data, size_t dlen, size_t *readbytes)
 
     if ((b->callback != NULL || b->callback_ex != NULL) &&
         ((ret = (int)bio_call_callback(b, BIO_CB_READ, data, dlen, 0, 0L, 1L,
-                                       readbytes)) <= 0))
+                                       NULL)) <= 0))
         return ret;
 
     if (!b->init) {
@@ -331,7 +332,7 @@ static int bio_write_intern(BIO *b, const void *data, size_t dlen,
 
     if ((b->callback != NULL || b->callback_ex != NULL) &&
         ((ret = (int)bio_call_callback(b, BIO_CB_WRITE, data, dlen, 0, 0L, 1L,
-                                       written)) <= 0))
+                                       NULL)) <= 0))
         return ret;
 
     if (!b->init) {
@@ -435,7 +436,7 @@ int BIO_gets(BIO *b, char *buf, int size)
 
     if ((b == NULL) || (b->method == NULL) || (b->method->bgets == NULL)) {
         BIOerr(BIO_F_BIO_GETS, BIO_R_UNSUPPORTED_METHOD);
-        return (-2);
+        return -2;
     }
 
     if (size < 0) {
@@ -451,7 +452,7 @@ int BIO_gets(BIO *b, char *buf, int size)
 
     if (!b->init) {
         BIOerr(BIO_F_BIO_GETS, BIO_R_UNINITIALIZED);
-        return (-2);
+        return -2;
     }
 
     ret = b->method->bgets(b, buf, size);
@@ -493,7 +494,7 @@ long BIO_int_ctrl(BIO *b, int cmd, long larg, int iarg)
     int i;
 
     i = iarg;
-    return (BIO_ctrl(b, cmd, larg, (char *)&i));
+    return BIO_ctrl(b, cmd, larg, (char *)&i);
 }
 
 void *BIO_ptr_ctrl(BIO *b, int cmd, long larg)
@@ -501,9 +502,9 @@ void *BIO_ptr_ctrl(BIO *b, int cmd, long larg)
     void *p = NULL;
 
     if (BIO_ctrl(b, cmd, larg, (char *)&p) <= 0)
-        return (NULL);
+        return NULL;
     else
-        return (p);
+        return p;
 }
 
 long BIO_ctrl(BIO *b, int cmd, long larg, void *parg)
@@ -533,18 +534,17 @@ long BIO_ctrl(BIO *b, int cmd, long larg, void *parg)
     return ret;
 }
 
-long BIO_callback_ctrl(BIO *b, int cmd,
-                       void (*fp) (struct bio_st *, int, const char *, int,
-                                   long, long))
+long BIO_callback_ctrl(BIO *b, int cmd, BIO_info_cb *fp)
 {
     long ret;
 
     if (b == NULL)
-        return (0);
+        return 0;
 
-    if ((b->method == NULL) || (b->method->callback_ctrl == NULL)) {
+    if ((b->method == NULL) || (b->method->callback_ctrl == NULL)
+            || (cmd != BIO_CTRL_SET_CALLBACK)) {
         BIOerr(BIO_F_BIO_CALLBACK_CTRL, BIO_R_UNSUPPORTED_METHOD);
-        return (-2);
+        return -2;
     }
 
     if (b->callback != NULL || b->callback_ex != NULL) {
@@ -584,7 +584,7 @@ BIO *BIO_push(BIO *b, BIO *bio)
     BIO *lb;
 
     if (b == NULL)
-        return (bio);
+        return bio;
     lb = b;
     while (lb->next_bio != NULL)
         lb = lb->next_bio;
@@ -593,7 +593,7 @@ BIO *BIO_push(BIO *b, BIO *bio)
         bio->prev_bio = lb;
     /* called to do internal processing */
     BIO_ctrl(b, BIO_CTRL_PUSH, 0, lb);
-    return (b);
+    return b;
 }
 
 /* Remove the first and return the rest */
@@ -602,7 +602,7 @@ BIO *BIO_pop(BIO *b)
     BIO *ret;
 
     if (b == NULL)
-        return (NULL);
+        return NULL;
     ret = b->next_bio;
 
     BIO_ctrl(b, BIO_CTRL_POP, 0, b);
@@ -614,7 +614,7 @@ BIO *BIO_pop(BIO *b)
 
     b->next_bio = NULL;
     b->prev_bio = NULL;
-    return (ret);
+    return ret;
 }
 
 BIO *BIO_get_retry_BIO(BIO *bio, int *reason)
@@ -632,12 +632,12 @@ BIO *BIO_get_retry_BIO(BIO *bio, int *reason)
     }
     if (reason != NULL)
         *reason = last->retry_reason;
-    return (last);
+    return last;
 }
 
 int BIO_get_retry_reason(BIO *bio)
 {
-    return (bio->retry_reason);
+    return bio->retry_reason;
 }
 
 void BIO_set_retry_reason(BIO *bio, int reason)
@@ -658,13 +658,13 @@ BIO *BIO_find_type(BIO *bio, int type)
 
             if (!mask) {
                 if (mt & type)
-                    return (bio);
+                    return bio;
             } else if (mt == type)
-                return (bio);
+                return bio;
         }
         bio = bio->next_bio;
     } while (bio != NULL);
-    return (NULL);
+    return NULL;
 }
 
 BIO *BIO_next(BIO *b)
@@ -732,11 +732,11 @@ BIO *BIO_dup_chain(BIO *in)
             eoc = new_bio;
         }
     }
-    return (ret);
+    return ret;
  err:
     BIO_free_all(ret);
 
-    return (NULL);
+    return NULL;
 }
 
 void BIO_copy_next_retry(BIO *b)
@@ -747,12 +747,12 @@ void BIO_copy_next_retry(BIO *b)
 
 int BIO_set_ex_data(BIO *bio, int idx, void *data)
 {
-    return (CRYPTO_set_ex_data(&(bio->ex_data), idx, data));
+    return CRYPTO_set_ex_data(&(bio->ex_data), idx, data);
 }
 
-void *BIO_get_ex_data(BIO *bio, int idx)
+void *BIO_get_ex_data(const BIO *bio, int idx)
 {
-    return (CRYPTO_get_ex_data(&(bio->ex_data), idx));
+    return CRYPTO_get_ex_data(&(bio->ex_data), idx);
 }
 
 uint64_t BIO_number_read(BIO *bio)
@@ -784,3 +784,120 @@ void bio_cleanup(void)
     CRYPTO_THREAD_lock_free(bio_type_lock);
     bio_type_lock = NULL;
 }
+
+/* Internal variant of the below BIO_wait() not calling BIOerr() */
+static int bio_wait(BIO *bio, time_t max_time, unsigned int nap_milliseconds)
+{
+#ifndef OPENSSL_NO_SOCK
+    int fd;
+#endif
+    long sec_diff;
+
+    if (max_time == 0) /* no timeout */
+        return 1;
+
+#ifndef OPENSSL_NO_SOCK
+    if (BIO_get_fd(bio, &fd) > 0 && fd < FD_SETSIZE)
+        return BIO_socket_wait(fd, BIO_should_read(bio), max_time);
+#endif
+    /* fall back to polling since no sockets are available */
+
+    sec_diff = (long)(max_time - time(NULL)); /* might overflow */
+    if (sec_diff < 0)
+        return 0; /* clearly timeout */
+
+    /* now take a nap at most the given number of milliseconds */
+    if (sec_diff == 0) { /* we are below the 1 seconds resolution of max_time */
+        if (nap_milliseconds > 1000)
+            nap_milliseconds = 1000;
+    } else { /* for sec_diff > 0, take min(sec_diff * 1000, nap_milliseconds) */
+        if ((unsigned long)sec_diff * 1000 < nap_milliseconds)
+            nap_milliseconds = (unsigned int)sec_diff * 1000;
+    }
+    ossl_sleep(nap_milliseconds);
+    return 1;
+}
+
+/*-
+ * Wait on (typically socket-based) BIO at most until max_time.
+ * Succeed immediately if max_time == 0.
+ * If sockets are not available support polling: succeed after waiting at most
+ * the number of nap_milliseconds in order to avoid a tight busy loop.
+ * Call BIOerr(...) on timeout or error.
+ * Returns -1 on error, 0 on timeout, and 1 on success.
+ */
+int BIO_wait(BIO *bio, time_t max_time, unsigned int nap_milliseconds)
+{
+    int rv = bio_wait(bio, max_time, nap_milliseconds);
+
+    if (rv <= 0)
+        BIOerr(0, rv == 0 ? BIO_R_TRANSFER_TIMEOUT : BIO_R_TRANSFER_ERROR);
+    return rv;
+}
+
+/*
+ * Connect via given BIO using BIO_do_connect() until success/timeout/error.
+ * Parameter timeout == 0 means no timeout, < 0 means exactly one try.
+ * For non-blocking and potentially even non-socket BIOs perform polling with
+ * the given density: between polls sleep nap_milliseconds using BIO_wait()
+ * in order to avoid a tight busy loop.
+ * Returns -1 on error, 0 on timeout, and 1 on success.
+ */
+int BIO_do_connect_retry(BIO *bio, int timeout, int nap_milliseconds)
+{
+    int blocking = timeout <= 0;
+    time_t max_time = timeout > 0 ? time(NULL) + timeout : 0;
+    int rv;
+
+    if (bio == NULL) {
+        BIOerr(0, ERR_R_PASSED_NULL_PARAMETER);
+        return -1;
+    }
+
+    if (nap_milliseconds < 0)
+        nap_milliseconds = 100;
+    BIO_set_nbio(bio, !blocking);
+
+ retry:
+    rv = BIO_do_connect(bio); /* This may indirectly call ERR_clear_error(); */
+
+    if (rv <= 0) { /* could be timeout or retryable error or fatal error */
+        int err = ERR_peek_last_error();
+        int reason = ERR_GET_REASON(err);
+        int do_retry = BIO_should_retry(bio); /* may be 1 only if !blocking */
+
+        if (ERR_GET_LIB(err) == ERR_LIB_BIO) {
+            switch (reason) {
+            case ERR_R_SYS_LIB:
+                /*
+                 * likely retryable system error occurred, which may be
+                 * EAGAIN (resource temporarily unavailable) some 40 secs after
+                 * calling getaddrinfo(): Temporary failure in name resolution
+                 * or a premature ETIMEDOUT, some 30 seconds after connect()
+                 */
+            case BIO_R_CONNECT_ERROR:
+            case BIO_R_NBIO_CONNECT_ERROR:
+                /* some likely retryable connection error occurred */
+                (void)BIO_reset(bio); /* often needed to avoid retry failure */
+                do_retry = 1;
+                break;
+            default:
+                break;
+            }
+        }
+        if (timeout >= 0 && do_retry) {
+            ERR_clear_error(); /* using ERR_pop_to_mark() would be cleaner */
+            /* will not actually wait if timeout == 0 (i.e., blocking BIO): */
+            rv = bio_wait(bio, max_time, nap_milliseconds);
+            if (rv > 0)
+                goto retry;
+            BIOerr(0, rv == 0 ? BIO_R_CONNECT_TIMEOUT : BIO_R_CONNECT_ERROR);
+        } else {
+            rv = -1;
+            if (err == 0) /* missing error queue entry */
+                BIOerr(0, BIO_R_CONNECT_ERROR); /* workaround: general error */
+        }
+    }
+
+    return rv;
+}