| // Copyright 1995-2016 The OpenSSL Project Authors. All Rights Reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include <openssl/bio.h> | |
| #if !defined(OPENSSL_NO_SOCK) | |
| #include <assert.h> | |
| #include <errno.h> | |
| #include <limits.h> | |
| #include <string.h> | |
| #if !defined(OPENSSL_WINDOWS) | |
| #include <arpa/inet.h> | |
| #include <netinet/in.h> | |
| #include <sys/socket.h> | |
| #include <unistd.h> | |
| #else | |
| #include <winsock2.h> | |
| #include <ws2tcpip.h> | |
| #endif | |
| #include <algorithm> | |
| #include <utility> | |
| #include <openssl/err.h> | |
| #include <openssl/mem.h> | |
| #include "../internal.h" | |
| #include "../mem_internal.h" | |
| #include "internal.h" | |
| using namespace bssl; | |
| enum { | |
| BIO_CONN_S_BEFORE, | |
| BIO_CONN_S_BLOCKED_CONNECT, | |
| BIO_CONN_S_OK, | |
| }; | |
| namespace { | |
| struct BIO_CONNECT { | |
| int state = BIO_CONN_S_BEFORE; | |
| UniquePtr<char> param_hostname; | |
| UniquePtr<char> param_port; | |
| int nbio = 0; | |
| unsigned short port = 0; | |
| sockaddr_storage them; | |
| socklen_t them_length = 0; | |
| // The file descriptor is kept in bio->num in order to match the socket BIO. | |
| // info_callback is called when the connection is initially made | |
| // callback(BIO,state,ret); The callback should return 'ret', state is for | |
| // compatibility with the SSL info_callback. | |
| int (*info_callback)(BIO *bio, int state, int ret) = nullptr; | |
| }; | |
| } // namespace | |
| #if !defined(OPENSSL_WINDOWS) | |
| static int closesocket(int sock) { return close(sock); } | |
| #endif | |
| // 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(UniquePtr<char> *out_host, | |
| UniquePtr<char> *out_port, const char *name) { | |
| const char *host, *port = nullptr; | |
| size_t host_len = 0; | |
| *out_host = nullptr; | |
| *out_port = nullptr; | |
| if (name[0] == '[') { // bracketed IPv6 address | |
| const char *close = strchr(name, ']'); | |
| if (close == nullptr) { | |
| 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 == nullptr || | |
| strchr(colon + 1, ':') != nullptr) { // IPv6 address | |
| host = name; | |
| host_len = strlen(name); | |
| } else { // host:port | |
| host = name; | |
| host_len = colon - name; | |
| port = colon + 1; | |
| } | |
| } | |
| out_host->reset(OPENSSL_strndup(host, host_len)); | |
| if (*out_host == nullptr) { | |
| return 0; | |
| } | |
| if (port == nullptr) { | |
| *out_port = nullptr; | |
| return 1; | |
| } | |
| out_port->reset(OPENSSL_strdup(port)); | |
| if (*out_port == nullptr) { | |
| *out_host = nullptr; | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| static int conn_state(BIO *bio, BIO_CONNECT *c) { | |
| int ret = -1, i; | |
| int (*cb)(BIO *, int, int) = nullptr; | |
| if (c->info_callback != nullptr) { | |
| cb = c->info_callback; | |
| } | |
| for (;;) { | |
| switch (c->state) { | |
| case BIO_CONN_S_BEFORE: | |
| // 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 == nullptr) { | |
| OPENSSL_PUT_ERROR(BIO, BIO_R_NO_HOSTNAME_SPECIFIED); | |
| goto exit_loop; | |
| } | |
| if (c->param_port == nullptr) { | |
| UniquePtr<char> host, port; | |
| if (!split_host_and_port(&host, &port, c->param_hostname.get()) || | |
| port == nullptr) { | |
| OPENSSL_PUT_ERROR(BIO, BIO_R_NO_PORT_SPECIFIED); | |
| ERR_add_error_data(2, "host=", c->param_hostname.get()); | |
| goto exit_loop; | |
| } | |
| c->param_port = std::move(port); | |
| c->param_hostname = std::move(host); | |
| } | |
| if (!bio_ip_and_port_to_socket_and_addr( | |
| &FromOpaque(bio)->num, &c->them, &c->them_length, | |
| c->param_hostname.get(), c->param_port.get())) { | |
| OPENSSL_PUT_ERROR(BIO, BIO_R_UNABLE_TO_CREATE_SOCKET); | |
| ERR_add_error_data(4, "host=", c->param_hostname.get(), ":", | |
| c->param_port.get()); | |
| goto exit_loop; | |
| } | |
| if (c->nbio) { | |
| if (!bio_socket_nbio(FromOpaque(bio)->num, 1)) { | |
| OPENSSL_PUT_ERROR(BIO, BIO_R_ERROR_SETTING_NBIO); | |
| ERR_add_error_data(4, "host=", c->param_hostname.get(), ":", | |
| c->param_port.get()); | |
| goto exit_loop; | |
| } | |
| } | |
| i = 1; | |
| ret = setsockopt(FromOpaque(bio)->num, SOL_SOCKET, SO_KEEPALIVE, | |
| (char *)&i, sizeof(i)); | |
| if (ret < 0) { | |
| OPENSSL_PUT_SYSTEM_ERROR(); | |
| OPENSSL_PUT_ERROR(BIO, BIO_R_KEEPALIVE); | |
| ERR_add_error_data(4, "host=", c->param_hostname.get(), ":", | |
| c->param_port.get()); | |
| goto exit_loop; | |
| } | |
| BIO_clear_retry_flags(bio); | |
| ret = connect(FromOpaque(bio)->num, (struct sockaddr *)&c->them, | |
| c->them_length); | |
| if (ret < 0) { | |
| if (bio_socket_should_retry(ret)) { | |
| BIO_set_retry_special(bio); | |
| c->state = BIO_CONN_S_BLOCKED_CONNECT; | |
| BIO_set_retry_reason(bio, BIO_RR_CONNECT); | |
| } else { | |
| OPENSSL_PUT_SYSTEM_ERROR(); | |
| OPENSSL_PUT_ERROR(BIO, BIO_R_CONNECT_ERROR); | |
| ERR_add_error_data(4, "host=", c->param_hostname.get(), ":", | |
| c->param_port.get()); | |
| } | |
| goto exit_loop; | |
| } else { | |
| c->state = BIO_CONN_S_OK; | |
| } | |
| break; | |
| case BIO_CONN_S_BLOCKED_CONNECT: | |
| if (!bio_socket_finish_connect(FromOpaque(bio)->num)) { | |
| if (bio_socket_should_retry(-1)) { | |
| BIO_set_retry_special(bio); | |
| c->state = BIO_CONN_S_BLOCKED_CONNECT; | |
| BIO_set_retry_reason(bio, BIO_RR_CONNECT); | |
| } else { | |
| BIO_clear_retry_flags(bio); | |
| OPENSSL_PUT_SYSTEM_ERROR(); | |
| OPENSSL_PUT_ERROR(BIO, BIO_R_NBIO_CONNECT_ERROR); | |
| ERR_add_error_data(4, "host=", c->param_hostname.get(), ":", | |
| c->param_port.get()); | |
| } | |
| ret = -1; | |
| goto exit_loop; | |
| } else { | |
| c->state = BIO_CONN_S_OK; | |
| } | |
| break; | |
| case BIO_CONN_S_OK: | |
| ret = 1; | |
| goto exit_loop; | |
| default: | |
| assert(0); | |
| goto exit_loop; | |
| } | |
| if (cb != nullptr) { | |
| ret = cb((BIO *)bio, c->state, ret); | |
| if (ret == 0) { | |
| goto end; | |
| } | |
| } | |
| } | |
| exit_loop: | |
| if (cb != nullptr) { | |
| ret = cb((BIO *)bio, c->state, ret); | |
| } | |
| end: | |
| return ret; | |
| } | |
| static int conn_new(BIO *bio) { | |
| BIO_set_init(bio, 0); | |
| FromOpaque(bio)->num = -1; | |
| FromOpaque(bio)->flags = 0; | |
| BIO_set_data(bio, New<BIO_CONNECT>()); | |
| return BIO_get_data(bio) != nullptr; | |
| } | |
| static void conn_close_socket(BIO *bio) { | |
| BIO_CONNECT *c = (BIO_CONNECT *)BIO_get_data(bio); | |
| if (FromOpaque(bio)->num == -1) { | |
| return; | |
| } | |
| // Only do a shutdown if things were established | |
| if (c->state == BIO_CONN_S_OK) { | |
| shutdown(FromOpaque(bio)->num, 2); | |
| } | |
| closesocket(FromOpaque(bio)->num); | |
| FromOpaque(bio)->num = -1; | |
| } | |
| static int conn_free(BIO *bio) { | |
| if (BIO_get_shutdown(bio)) { | |
| conn_close_socket(bio); | |
| } | |
| Delete(static_cast<BIO_CONNECT *>(BIO_get_data(bio))); | |
| return 1; | |
| } | |
| static int conn_read(BIO *bio, char *out, int out_len) { | |
| int ret = 0; | |
| BIO_CONNECT *data; | |
| data = (BIO_CONNECT *)BIO_get_data(bio); | |
| if (data->state != BIO_CONN_S_OK) { | |
| ret = conn_state(bio, data); | |
| if (ret <= 0) { | |
| return ret; | |
| } | |
| } | |
| bio_clear_socket_error(); | |
| ret = (int)recv(FromOpaque(bio)->num, out, out_len, 0); | |
| BIO_clear_retry_flags(bio); | |
| if (ret <= 0) { | |
| if (bio_socket_should_retry(ret)) { | |
| BIO_set_retry_read(bio); | |
| } | |
| } | |
| return ret; | |
| } | |
| static int conn_write_ex(BIO *bio, const char *in, size_t in_len, | |
| size_t *out_written) { | |
| BIO_CONNECT *data = (BIO_CONNECT *)BIO_get_data(bio); | |
| if (data->state != BIO_CONN_S_OK) { | |
| if (conn_state(bio, data) <= 0) { | |
| return 0; | |
| } | |
| } | |
| bio_clear_socket_error(); | |
| #if defined(OPENSSL_WINDOWS) | |
| in_len = std::min(in_len, size_t{INT_MAX}); | |
| int ret = send(FromOpaque(bio)->num, in, static_cast<int>(in_len), 0); | |
| #else | |
| ssize_t ret = send(FromOpaque(bio)->num, in, in_len, 0); | |
| #endif | |
| BIO_clear_retry_flags(bio); | |
| if (ret <= 0) { | |
| if (bio_socket_should_retry(ret)) { | |
| BIO_set_retry_write(bio); | |
| } | |
| return 0; | |
| } | |
| *out_written = ret; | |
| return 1; | |
| } | |
| static long conn_ctrl(BIO *bio, int cmd, long num, void *ptr) { | |
| BIO_CONNECT *data = static_cast<BIO_CONNECT *>(BIO_get_data(bio)); | |
| switch (cmd) { | |
| case BIO_CTRL_RESET: | |
| data->state = BIO_CONN_S_BEFORE; | |
| conn_close_socket(bio); | |
| FromOpaque(bio)->flags = 0; | |
| return 0; | |
| case BIO_C_DO_STATE_MACHINE: | |
| // use this one to start the connection | |
| if (data->state != BIO_CONN_S_OK) { | |
| return conn_state(bio, data); | |
| } else { | |
| return 1; | |
| } | |
| case BIO_C_SET_CONNECT: | |
| if (ptr == nullptr) { | |
| return 0; | |
| } | |
| BIO_set_init(bio, 1); | |
| if (num == 0) { | |
| data->param_hostname.reset( | |
| OPENSSL_strdup(reinterpret_cast<const char *>(ptr))); | |
| if (data->param_hostname == nullptr) { | |
| return 0; | |
| } | |
| } else if (num == 1) { | |
| data->param_port.reset( | |
| OPENSSL_strdup(reinterpret_cast<const char *>(ptr))); | |
| if (data->param_port == nullptr) { | |
| return 0; | |
| } | |
| } else { | |
| return 0; | |
| } | |
| return 1; | |
| case BIO_C_SET_NBIO: | |
| data->nbio = static_cast<int>(num); | |
| return 1; | |
| case BIO_C_GET_FD: | |
| if (BIO_get_init(bio)) { | |
| int *out = static_cast<int *>(ptr); | |
| if (out != nullptr) { | |
| *out = FromOpaque(bio)->num; | |
| } | |
| return FromOpaque(bio)->num; | |
| } else { | |
| return -1; | |
| } | |
| case BIO_CTRL_GET_CLOSE: | |
| return BIO_get_shutdown(bio); | |
| case BIO_CTRL_SET_CLOSE: | |
| BIO_set_shutdown(bio, static_cast<int>(num)); | |
| return 1; | |
| case BIO_CTRL_FLUSH: | |
| return 1; | |
| case BIO_CTRL_GET_CALLBACK: { | |
| auto out = reinterpret_cast<int (**)(BIO *bio, int state, int xret)>(ptr); | |
| *out = data->info_callback; | |
| return 1; | |
| } | |
| default: | |
| return 0; | |
| } | |
| } | |
| static long conn_callback_ctrl(BIO *bio, int cmd, BIO_info_cb *fp) { | |
| BIO_CONNECT *data = static_cast<BIO_CONNECT *>(BIO_get_data(bio)); | |
| switch (cmd) { | |
| case BIO_CTRL_SET_CALLBACK: | |
| data->info_callback = fp; | |
| return 1; | |
| default: | |
| return 0; | |
| } | |
| } | |
| BIO *BIO_new_connect(const char *hostname) { | |
| BIO *ret; | |
| ret = BIO_new(BIO_s_connect()); | |
| if (ret == nullptr) { | |
| return nullptr; | |
| } | |
| if (!BIO_set_conn_hostname(ret, hostname)) { | |
| BIO_free(ret); | |
| return nullptr; | |
| } | |
| return ret; | |
| } | |
| static const BIO_METHOD methods_connectp = { | |
| BIO_TYPE_CONNECT, "socket connect", /*bwrite=*/nullptr, conn_write_ex, | |
| conn_read, /*gets=*/nullptr, conn_ctrl, conn_new, | |
| conn_free, conn_callback_ctrl, | |
| }; | |
| const BIO_METHOD *BIO_s_connect() { return &methods_connectp; } | |
| int BIO_set_conn_hostname(BIO *bio, const char *name) { | |
| return (int)BIO_ctrl(bio, BIO_C_SET_CONNECT, 0, (void *)name); | |
| } | |
| int BIO_set_conn_port(BIO *bio, const char *port_str) { | |
| return (int)BIO_ctrl(bio, BIO_C_SET_CONNECT, 1, (void *)port_str); | |
| } | |
| int BIO_set_conn_int_port(BIO *bio, const int *port) { | |
| char buf[DECIMAL_SIZE(int) + 1]; | |
| snprintf(buf, sizeof(buf), "%d", *port); | |
| return BIO_set_conn_port(bio, buf); | |
| } | |
| int BIO_set_nbio(BIO *bio, int on) { | |
| return (int)BIO_ctrl(bio, BIO_C_SET_NBIO, on, nullptr); | |
| } | |
| int BIO_do_connect(BIO *bio) { | |
| return (int)BIO_ctrl(bio, BIO_C_DO_STATE_MACHINE, 0, nullptr); | |
| } | |
| #endif // OPENSSL_NO_SOCK |