From 04968b18c4a120f627510e05c1f799098d464d53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Sun, 1 Jun 2014 21:37:20 -0400 Subject: [PATCH] libfreerdp-core: replace all OpenSSL built-in BIOs by new full duplex BIOs --- libfreerdp/core/CMakeLists.txt | 2 +- libfreerdp/core/gateway/rpc.c | 3 +- libfreerdp/core/tcp.c | 273 +++++++++++++++--- libfreerdp/core/tcp.h | 3 +- libfreerdp/core/transport.c | 46 ++-- libfreerdp/crypto/tls.c | 458 ++++++++++++++++++++++++++++++- winpr/libwinpr/winsock/winsock.c | 19 +- 7 files changed, 731 insertions(+), 73 deletions(-) diff --git a/libfreerdp/core/CMakeLists.txt b/libfreerdp/core/CMakeLists.txt index 856dc767d..4f0f72bdc 100644 --- a/libfreerdp/core/CMakeLists.txt +++ b/libfreerdp/core/CMakeLists.txt @@ -148,7 +148,7 @@ set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS MONOLITHIC ${MONOLITHIC_BUILD} MODULE winpr - MODULES winpr-registry winpr-utils winpr-interlocked winpr-dsparse winpr-sspi winpr-rpc winpr-wtsapi winpr-handle winpr-crt) + MODULES winpr-registry winpr-utils winpr-interlocked winpr-dsparse winpr-sspi winpr-rpc winpr-wtsapi winpr-handle winpr-winsock winpr-crt) if(MONOLITHIC_BUILD) set(FREERDP_LIBS ${FREERDP_LIBS} ${${MODULE_PREFIX}_LIBS} PARENT_SCOPE) diff --git a/libfreerdp/core/gateway/rpc.c b/libfreerdp/core/gateway/rpc.c index 2432ab06c..ad177fe65 100644 --- a/libfreerdp/core/gateway/rpc.c +++ b/libfreerdp/core/gateway/rpc.c @@ -319,8 +319,7 @@ int rpc_out_read(rdpRpc* rpc, BYTE* data, int length) int status; status = BIO_read(rpc->TlsOut->bio, data, length); - /* fprintf(stderr, "%s: length=%d => status=%d shouldRetry=%d\n", __FUNCTION__, length, - * status, BIO_should_retry(rpc->TlsOut->bio)); */ + if (status > 0) { #ifdef HAVE_VALGRIND_MEMCHECK_H VALGRIND_MAKE_MEM_DEFINED(data, status); diff --git a/libfreerdp/core/tcp.c b/libfreerdp/core/tcp.c index c669aaa56..c3276860c 100644 --- a/libfreerdp/core/tcp.c +++ b/libfreerdp/core/tcp.c @@ -30,6 +30,7 @@ #include #include +#include #ifndef _WIN32 #include @@ -55,9 +56,6 @@ #endif #endif -#else -#define SHUT_RDWR SD_BOTH -#define close(_fd) closesocket(_fd) #endif #include @@ -66,6 +64,185 @@ #include "tcp.h" +/* Simple Socket BIO */ + +static int transport_bio_simple_new(BIO* bio); +static int transport_bio_simple_free(BIO* bio); + +long transport_bio_simple_callback(BIO* bio, int mode, const char* argp, int argi, long argl, long ret) +{ + return 1; +} + +static int transport_bio_simple_write(BIO* bio, const char* buf, int size) +{ + int error; + int status = 0; + + if (!buf) + return 0; + + BIO_clear_flags(bio, BIO_FLAGS_WRITE); + + status = _send((SOCKET) bio->num, buf, size, 0); + + if (status <= 0) + { + error = WSAGetLastError(); + + if ((error == WSAEWOULDBLOCK) || (error == WSAEINTR) || + (error == WSAEINPROGRESS) || (error == WSAEALREADY)) + { + BIO_set_flags(bio, (BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY)); + } + else + { + BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY); + } + } + + return status; +} + +static int transport_bio_simple_read(BIO* bio, char* buf, int size) +{ + int error; + int status = 0; + + if (!buf) + return 0; + + BIO_clear_flags(bio, BIO_FLAGS_READ); + + status = _recv((SOCKET) bio->num, buf, size, 0); + + if (status <= 0) + { + error = WSAGetLastError(); + + if ((error == WSAEWOULDBLOCK) || (error == WSAEINTR) || + (error == WSAEINPROGRESS) || (error == WSAEALREADY)) + { + BIO_set_flags(bio, (BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY)); + } + else + { + BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY); + } + } + + return status; +} + +static int transport_bio_simple_puts(BIO* bio, const char* str) +{ + return 1; +} + +static int transport_bio_simple_gets(BIO* bio, char* str, int size) +{ + return 1; +} + +static long transport_bio_simple_ctrl(BIO* bio, int cmd, long arg1, void* arg2) +{ + int status = -1; + + switch (cmd) + { + case BIO_C_SET_FD: + if (arg2) + { + transport_bio_simple_free(bio); + bio->flags = BIO_FLAGS_SHOULD_RETRY; + bio->num = *((int*) arg2); + bio->shutdown = (int) arg1; + bio->init = 1; + status = 1; + } + break; + + case BIO_C_GET_FD: + if (bio->init) + { + if (arg2) + *((int*) arg2) = bio->num; + status = bio->num; + } + break; + + case BIO_CTRL_GET_CLOSE: + status = bio->shutdown; + break; + + case BIO_CTRL_SET_CLOSE: + bio->shutdown = (int) arg1; + status = 1; + break; + + case BIO_CTRL_DUP: + status = 1; + break; + + case BIO_CTRL_FLUSH: + status = 1; + break; + + default: + status = 0; + break; + } + + return status; +} + +static int transport_bio_simple_new(BIO* bio) +{ + bio->init = 0; + bio->num = 0; + bio->ptr = NULL; + bio->flags = BIO_FLAGS_SHOULD_RETRY; + return 1; +} + +static int transport_bio_simple_free(BIO* bio) +{ + if (!bio) + return 0; + + if (bio->shutdown) + { + if (bio->init) + closesocket((SOCKET) bio->num); + + bio->init = 0; + bio->flags = 0; + } + + return 1; +} + +static BIO_METHOD transport_bio_simple_socket_methods = +{ + BIO_TYPE_SIMPLE, + "SimpleSocket", + transport_bio_simple_write, + transport_bio_simple_read, + transport_bio_simple_puts, + transport_bio_simple_gets, + transport_bio_simple_ctrl, + transport_bio_simple_new, + transport_bio_simple_free, + NULL, +}; + +BIO_METHOD* BIO_s_simple_socket(void) +{ + return &transport_bio_simple_socket_methods; +} + +/* Buffered Socket BIO */ + long transport_bio_buffered_callback(BIO* bio, int mode, const char* argp, int argi, long argl, long ret) { return 1; @@ -80,7 +257,7 @@ static int transport_bio_buffered_write(BIO* bio, const char* buf, int num) ret = num; tcp->writeBlocked = FALSE; - BIO_clear_retry_flags(bio); + BIO_clear_flags(bio, BIO_FLAGS_WRITE); /* we directly append extra bytes in the xmit buffer, this could be prevented * but for now it makes the code more simple. @@ -99,21 +276,22 @@ static int transport_bio_buffered_write(BIO* bio, const char* buf, int num) while (chunks[i].size) { status = BIO_write(bio->next_bio, chunks[i].data, chunks[i].size); - /*fprintf(stderr, "%s: i=%d/%d size=%d/%d status=%d retry=%d\n", __FUNCTION__, i, nchunks, - chunks[i].size, ringbuffer_used(&tcp->xmitBuffer), status, - BIO_should_retry(bio->next_bio) - );*/ + if (status <= 0) { - if (BIO_should_retry(bio->next_bio)) + if (!BIO_should_retry(bio->next_bio)) { + BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY); + ret = -1; /* fatal error */ + goto out; + } + + if (BIO_should_write(bio->next_bio)) + { + BIO_set_flags(bio, BIO_FLAGS_WRITE); tcp->writeBlocked = TRUE; goto out; /* EWOULDBLOCK */ } - - /* any other is an error, but we still have to commit written bytes */ - ret = -1; - goto out; } committedBytes += status; @@ -133,17 +311,30 @@ static int transport_bio_buffered_read(BIO* bio, char* buf, int size) rdpTcp* tcp = (rdpTcp*) bio->ptr; tcp->readBlocked = FALSE; - BIO_clear_retry_flags(bio); + BIO_clear_flags(bio, BIO_FLAGS_READ); status = BIO_read(bio->next_bio, buf, size); - /*fprintf(stderr, "%s: size=%d status=%d shouldRetry=%d\n", __FUNCTION__, size, status, BIO_should_retry(bio->next_bio)); */ - if (status <= 0 && BIO_should_retry(bio->next_bio)) + if (status <= 0) { - BIO_set_retry_read(bio); - tcp->readBlocked = TRUE; + if (!BIO_should_retry(bio->next_bio)) + { + BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY); + status = -1; + goto out; + } + + BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY); + + if (BIO_should_read(bio->next_bio)) + { + BIO_set_flags(bio, BIO_FLAGS_READ); + tcp->readBlocked = TRUE; + goto out; + } } +out: return status; } @@ -159,19 +350,21 @@ static int transport_bio_buffered_gets(BIO* bio, char* str, int size) static long transport_bio_buffered_ctrl(BIO* bio, int cmd, long arg1, void* arg2) { - rdpTcp *tcp = (rdpTcp *)bio->ptr; + rdpTcp* tcp = (rdpTcp*) bio->ptr; switch (cmd) { - case BIO_CTRL_FLUSH: - return 1; - case BIO_CTRL_WPENDING: - return ringbuffer_used(&tcp->xmitBuffer); - case BIO_CTRL_PENDING: - return 0; - default: - /*fprintf(stderr, "%s: passing to next BIO, bio=%p cmd=%d arg1=%d arg2=%p\n", __FUNCTION__, bio, cmd, arg1, arg2); */ - return BIO_ctrl(bio->next_bio, cmd, arg1, arg2); + case BIO_CTRL_FLUSH: + return 1; + + case BIO_CTRL_WPENDING: + return ringbuffer_used(&tcp->xmitBuffer); + + case BIO_CTRL_PENDING: + return 0; + + default: + return BIO_ctrl(bio->next_bio, cmd, arg1, arg2); } return 0; @@ -182,8 +375,7 @@ static int transport_bio_buffered_new(BIO* bio) bio->init = 1; bio->num = 0; bio->ptr = NULL; - bio->flags = 0; - + bio->flags = BIO_FLAGS_SHOULD_RETRY; return 1; } @@ -192,7 +384,6 @@ static int transport_bio_buffered_free(BIO* bio) return 1; } - static BIO_METHOD transport_bio_buffered_socket_methods = { BIO_TYPE_BUFFERED, @@ -214,18 +405,17 @@ BIO_METHOD* BIO_s_buffered_socket(void) BOOL transport_bio_buffered_drain(BIO *bio) { - rdpTcp *tcp = (rdpTcp *)bio->ptr; int status; + rdpTcp* tcp = (rdpTcp*) bio->ptr; if (!ringbuffer_used(&tcp->xmitBuffer)) return 1; status = transport_bio_buffered_write(bio, NULL, 0); + return status >= 0; } - - void tcp_get_ip_address(rdpTcp* tcp) { BYTE* ip; @@ -346,6 +536,16 @@ BOOL tcp_connect(rdpTcp* tcp, const char* hostname, int port, int timeout) return FALSE; /* timeout */ } } + + BIO_set_close(tcp->socketBio, BIO_NOCLOSE); + BIO_free(tcp->socketBio); + + tcp->socketBio = BIO_new(BIO_s_simple_socket()); + + if (!tcp->socketBio) + return -1; + + BIO_set_fd(tcp->socketBio, tcp->sockfd, BIO_CLOSE); } SetEventFileDescriptor(tcp->event, tcp->sockfd); @@ -504,9 +704,12 @@ int tcp_attach(rdpTcp* tcp, int sockfd) } else { - tcp->socketBio = BIO_new_socket(sockfd, 1); + tcp->socketBio = BIO_new(BIO_s_simple_socket()); + if (!tcp->socketBio) return -1; + + BIO_set_fd(tcp->socketBio, sockfd, BIO_CLOSE); } if (!tcp->bufferedBio) diff --git a/libfreerdp/core/tcp.h b/libfreerdp/core/tcp.h index 7ee2a74ca..8998f3856 100644 --- a/libfreerdp/core/tcp.h +++ b/libfreerdp/core/tcp.h @@ -38,7 +38,8 @@ #define MSG_NOSIGNAL 0 #endif -#define BIO_TYPE_BUFFERED 66 +#define BIO_TYPE_SIMPLE 66 +#define BIO_TYPE_BUFFERED 67 typedef struct rdp_tcp rdpTcp; diff --git a/libfreerdp/core/transport.c b/libfreerdp/core/transport.c index e5fd120ef..a4b46e2c4 100644 --- a/libfreerdp/core/transport.c +++ b/libfreerdp/core/transport.c @@ -133,17 +133,20 @@ static int transport_bio_tsg_write(BIO* bio, const char* buf, int num) tsg = (rdpTsg*) bio->ptr; - BIO_clear_retry_flags(bio); + BIO_clear_flags(bio, BIO_FLAGS_WRITE); status = tsg_write(tsg, (BYTE*) buf, num); - if (status == 0) - BIO_set_retry_write(bio); + if (status < 0) + { + BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY); + } + else + { + BIO_set_flags(bio, BIO_FLAGS_WRITE); + } - if (status > 0) - return status; - - return -1; + return status >= 0 ? status : -1; } static int transport_bio_tsg_read(BIO* bio, char* buf, int size) @@ -153,18 +156,17 @@ static int transport_bio_tsg_read(BIO* bio, char* buf, int size) tsg = (rdpTsg*) bio->ptr; + BIO_clear_flags(bio, BIO_FLAGS_READ); + status = tsg_read(bio->ptr, (BYTE*) buf, size); - BIO_clear_retry_flags(bio); - - if (status == 0) + if (status < 0) { - BIO_set_retry_read(bio); - status = -1; + BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY); } - else if (status == -1) + else { - status = 0; + BIO_set_flags(bio, BIO_FLAGS_READ); } return status >= 0 ? status : -1; @@ -195,8 +197,7 @@ static int transport_bio_tsg_new(BIO* bio) bio->init = 1; bio->num = 0; bio->ptr = NULL; - bio->flags = 0; - + bio->flags = BIO_FLAGS_SHOULD_RETRY; return 1; } @@ -226,8 +227,6 @@ BIO_METHOD* BIO_s_tsg(void) return &transport_bio_tsg_methods; } - - BOOL transport_connect_tls(rdpTransport* transport) { rdpSettings *settings = transport->settings; @@ -708,7 +707,6 @@ static int transport_wait_for_write(rdpTransport* transport) return select(tcpOut->sockfd + 1, rsetPtr, wsetPtr, NULL, &tv); } - int transport_read_layer(rdpTransport* transport, BYTE* data, int bytes) { int read = 0; @@ -724,13 +722,7 @@ int transport_read_layer(rdpTransport* transport, BYTE* data, int bytes) { status = BIO_read(transport->frontBio, data + read, bytes - read); - if (!status) - { - transport->layer = TRANSPORT_LAYER_CLOSED; - return -1; - } - - if (status < 0) + if (status <= 0) { if (!transport->frontBio || !BIO_should_retry(transport->frontBio)) { @@ -763,8 +755,6 @@ int transport_read_layer(rdpTransport* transport, BYTE* data, int bytes) return read; } - - int transport_read(rdpTransport* transport, wStream* s) { int status; diff --git a/libfreerdp/crypto/tls.c b/libfreerdp/crypto/tls.c index a52d75fe3..a02f7adac 100644 --- a/libfreerdp/crypto/tls.c +++ b/libfreerdp/crypto/tls.c @@ -33,6 +33,445 @@ #include #include "../core/tcp.h" +struct _BIO_RDP_TLS +{ + SSL* ssl; +}; +typedef struct _BIO_RDP_TLS BIO_RDP_TLS; + +long bio_rdp_tls_callback(BIO* bio, int mode, const char* argp, int argi, long argl, long ret) +{ + return 1; +} + +static int bio_rdp_tls_write(BIO* bio, const char* buf, int size) +{ + int status; + BIO_RDP_TLS* tls = (BIO_RDP_TLS*) bio->ptr; + + if (!buf || !tls) + return 0; + + BIO_clear_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_READ | BIO_FLAGS_IO_SPECIAL); + + status = SSL_write(tls->ssl, buf, size); + + if (status <= 0) + { + switch (SSL_get_error(tls->ssl, status)) + { + case SSL_ERROR_NONE: + BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY); + break; + + case SSL_ERROR_WANT_WRITE: + BIO_set_flags(bio, BIO_FLAGS_WRITE); + break; + + case SSL_ERROR_WANT_READ: + BIO_set_flags(bio, BIO_FLAGS_READ); + break; + + case SSL_ERROR_WANT_X509_LOOKUP: + BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL); + bio->retry_reason = BIO_RR_SSL_X509_LOOKUP; + break; + + case SSL_ERROR_WANT_CONNECT: + BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL); + bio->retry_reason = BIO_RR_CONNECT; + break; + + case SSL_ERROR_SYSCALL: + BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY); + break; + + case SSL_ERROR_SSL: + BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY); + break; + } + } + + return status; +} + +static int bio_rdp_tls_read(BIO* bio, char* buf, int size) +{ + int status; + BIO_RDP_TLS* tls = (BIO_RDP_TLS*) bio->ptr; + + if (!buf || !tls) + return 0; + + BIO_clear_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_READ | BIO_FLAGS_IO_SPECIAL); + + status = SSL_read(tls->ssl, buf, size); + + if (status <= 0) + { + switch (SSL_get_error(tls->ssl, status)) + { + case SSL_ERROR_NONE: + BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY); + break; + + case SSL_ERROR_WANT_READ: + BIO_set_flags(bio, BIO_FLAGS_READ); + break; + + case SSL_ERROR_WANT_WRITE: + BIO_set_flags(bio, BIO_FLAGS_WRITE); + break; + + case SSL_ERROR_WANT_X509_LOOKUP: + BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL); + bio->retry_reason = BIO_RR_SSL_X509_LOOKUP; + break; + + case SSL_ERROR_WANT_ACCEPT: + BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL); + bio->retry_reason = BIO_RR_ACCEPT; + break; + + case SSL_ERROR_WANT_CONNECT: + BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL); + bio->retry_reason = BIO_RR_CONNECT; + break; + + case SSL_ERROR_SSL: + BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY); + break; + + case SSL_ERROR_ZERO_RETURN: + BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY); + break; + + case SSL_ERROR_SYSCALL: + status = 0; + break; + } + } + + return status; +} + +static int bio_rdp_tls_puts(BIO* bio, const char* str) +{ + int size; + int status; + + if (!str) + return 0; + + size = strlen(str); + status = BIO_write(bio, str, size); + + return status; +} + +static int bio_rdp_tls_gets(BIO* bio, char* str, int size) +{ + return 1; +} + +static long bio_rdp_tls_ctrl(BIO* bio, int cmd, long num, void* ptr) +{ + BIO* rbio; + int status = -1; + BIO_RDP_TLS* tls = (BIO_RDP_TLS*) bio->ptr; + + if (!tls) + return 0; + + if (!tls->ssl && (cmd != BIO_C_SET_SSL)) + return 0; + + switch (cmd) + { + case BIO_CTRL_RESET: + SSL_shutdown(tls->ssl); + + if (tls->ssl->handshake_func == tls->ssl->method->ssl_connect) + SSL_set_connect_state(tls->ssl); + else if (tls->ssl->handshake_func == tls->ssl->method->ssl_accept) + SSL_set_accept_state(tls->ssl); + + SSL_clear(tls->ssl); + + if (bio->next_bio) + status = BIO_ctrl(bio->next_bio, cmd, num, ptr); + else if (tls->ssl->rbio) + status = BIO_ctrl(tls->ssl->rbio, cmd, num, ptr); + else + status = 1; + break; + + case BIO_C_GET_FD: + status = BIO_ctrl(tls->ssl->rbio, cmd, num, ptr); + break; + + case BIO_CTRL_INFO: + status = 0; + break; + + case BIO_CTRL_SET_CALLBACK: + status = 0; + break; + + case BIO_CTRL_GET_CALLBACK: + *((ULONG_PTR*) ptr) = (ULONG_PTR) SSL_get_info_callback(tls->ssl); + status = 1; + break; + + case BIO_C_SSL_MODE: + if (num) + SSL_set_connect_state(tls->ssl); + else + SSL_set_accept_state(tls->ssl); + status = 1; + break; + + case BIO_CTRL_GET_CLOSE: + status = bio->shutdown; + break; + + case BIO_CTRL_SET_CLOSE: + bio->shutdown = (int) num; + status = 1; + break; + + case BIO_CTRL_WPENDING: + status = BIO_ctrl(tls->ssl->wbio, cmd, num, ptr); + break; + + case BIO_CTRL_PENDING: + status = SSL_pending(tls->ssl); + if (status == 0) + status = BIO_pending(tls->ssl->rbio); + break; + + case BIO_CTRL_FLUSH: + BIO_clear_retry_flags(bio); + status = BIO_ctrl(tls->ssl->wbio, cmd, num, ptr); + BIO_copy_next_retry(bio); + status = 1; + break; + + case BIO_CTRL_PUSH: + if (bio->next_bio && (bio->next_bio != tls->ssl->rbio)) + { + SSL_set_bio(tls->ssl, bio->next_bio, bio->next_bio); + CRYPTO_add(&(bio->next_bio->references), 1, CRYPTO_LOCK_BIO); + } + status = 1; + break; + + case BIO_CTRL_POP: + if (bio == ptr) + { + if (tls->ssl->rbio != tls->ssl->wbio) + BIO_free_all(tls->ssl->wbio); + + if (bio->next_bio) + CRYPTO_add(&(bio->next_bio->references), -1, CRYPTO_LOCK_BIO); + + tls->ssl->wbio = tls->ssl->rbio = NULL; + } + status = 1; + break; + + case BIO_C_GET_SSL: + if (ptr) + { + *((SSL**) ptr) = tls->ssl; + status = 1; + } + break; + + case BIO_C_SET_SSL: + bio->shutdown = (int) num; + + if (ptr) + tls->ssl = (SSL*) ptr; + + rbio = SSL_get_rbio(tls->ssl); + + if (rbio) + { + if (bio->next_bio) + BIO_push(rbio, bio->next_bio); + + bio->next_bio = rbio; + CRYPTO_add(&(rbio->references), 1, CRYPTO_LOCK_BIO); + } + + bio->init = 1; + status = 1; + break; + + case BIO_C_DO_STATE_MACHINE: + BIO_clear_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_WRITE | BIO_FLAGS_IO_SPECIAL); + bio->retry_reason = 0; + + status = SSL_do_handshake(tls->ssl); + + if (status <= 0) + { + switch (SSL_get_error(tls->ssl, status)) + { + case SSL_ERROR_WANT_READ: + BIO_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY); + break; + + case SSL_ERROR_WANT_WRITE: + BIO_set_flags(bio, BIO_FLAGS_WRITE | BIO_FLAGS_SHOULD_RETRY); + break; + + case SSL_ERROR_WANT_CONNECT: + BIO_set_flags(bio, BIO_FLAGS_IO_SPECIAL | BIO_FLAGS_SHOULD_RETRY); + bio->retry_reason = bio->next_bio->retry_reason; + break; + + default: + BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY); + break; + } + } + break; + + default: + status = BIO_ctrl(tls->ssl->rbio, cmd, num, ptr); + break; + } + + return status; +} + +static int bio_rdp_tls_new(BIO* bio) +{ + BIO_RDP_TLS* tls; + + bio->init = 0; + bio->num = 0; + bio->flags = BIO_FLAGS_SHOULD_RETRY; + bio->next_bio = NULL; + + tls = calloc(1, sizeof(BIO_RDP_TLS)); + + if (!tls) + return 0; + + bio->ptr = (void*) tls; + + return 1; +} + +static int bio_rdp_tls_free(BIO* bio) +{ + BIO_RDP_TLS* tls; + + if (!bio) + return 0; + + tls = (BIO_RDP_TLS*) bio->ptr; + + if (!tls) + return 0; + + if (bio->shutdown) + { + if (bio->init && tls->ssl) + { + SSL_shutdown(tls->ssl); + SSL_free(tls->ssl); + } + + bio->init = 0; + bio->flags = 0; + } + + free(tls); + + return 1; +} + +static long bio_rdp_tls_callback_ctrl(BIO* bio, int cmd, bio_info_cb* fp) +{ + int status = 0; + BIO_RDP_TLS* tls; + + if (!bio) + return 0; + + tls = (BIO_RDP_TLS*) bio->ptr; + + if (!tls) + return 0; + + switch (cmd) + { + case BIO_CTRL_SET_CALLBACK: + SSL_set_info_callback(tls->ssl, (void (*)(const SSL *, int, int)) fp); + status = 1; + break; + + default: + status = BIO_callback_ctrl(tls->ssl->rbio, cmd, fp); + break; + } + + return status; +} + +#define BIO_TYPE_RDP_TLS 68 + +static BIO_METHOD bio_rdp_tls_methods = +{ + BIO_TYPE_RDP_TLS, + "RdpTls", + bio_rdp_tls_write, + bio_rdp_tls_read, + bio_rdp_tls_puts, + bio_rdp_tls_gets, + bio_rdp_tls_ctrl, + bio_rdp_tls_new, + bio_rdp_tls_free, + bio_rdp_tls_callback_ctrl, +}; + +BIO_METHOD* BIO_s_rdp_tls(void) +{ + return &bio_rdp_tls_methods; +} + +BIO* BIO_new_rdp_tls(SSL_CTX* ctx, int client) +{ + BIO* bio; + SSL* ssl; + + bio = BIO_new(BIO_s_rdp_tls()); + + if (!bio) + return NULL; + + ssl = SSL_new(ctx); + + if (!ssl) + { + BIO_free(bio); + return NULL; + } + + if (client) + SSL_set_connect_state(ssl); + else + SSL_set_accept_state(ssl); + + BIO_set_ssl(bio, ssl, BIO_CLOSE); + + return bio; +} + static CryptoCert tls_get_certificate(rdpTls* tls, BOOL peer) { CryptoCert cert; @@ -127,7 +566,8 @@ BOOL tls_prepare(rdpTls* tls, BIO *underlying, const SSL_METHOD *method, int opt SSL_CTX_set_options(tls->ctx, options); SSL_CTX_set_read_ahead(tls->ctx, 1); - tls->bio = BIO_new_ssl(tls->ctx, clientMode); + tls->bio = BIO_new_rdp_tls(tls->ctx, clientMode); + if (BIO_get_ssl(tls->bio, &tls->ssl) < 0) { fprintf(stderr, "%s: unable to retrieve the SSL of the connection\n", __FUNCTION__); @@ -135,6 +575,7 @@ BOOL tls_prepare(rdpTls* tls, BIO *underlying, const SSL_METHOD *method, int opt } BIO_push(tls->bio, underlying); + return TRUE; } @@ -150,8 +591,10 @@ int tls_do_handshake(rdpTls* tls, BOOL clientMode) int fd; status = BIO_do_handshake(tls->bio); + if (status == 1) break; + if (!BIO_should_retry(tls->bio)) return -1; @@ -160,6 +603,7 @@ int tls_do_handshake(rdpTls* tls, BOOL clientMode) FD_ZERO(&rset); fd = BIO_get_fd(tls->bio, NULL); + if (fd < 0) { fprintf(stderr, "%s: unable to retrieve BIO fd\n", __FUNCTION__); @@ -171,6 +615,7 @@ int tls_do_handshake(rdpTls* tls, BOOL clientMode) tv.tv_usec = 10 * 1000; /* 10ms */ status = select(fd + 1, &rset, NULL, NULL, &tv); + if (status < 0) { fprintf(stderr, "%s: error during select()\n", __FUNCTION__); @@ -383,7 +828,7 @@ int tls_write_all(rdpTls* tls, const BYTE* data, int length) fd_set rset, wset; fd_set *rsetPtr, *wsetPtr; struct timeval tv; - BIO *bio = tls->bio; + BIO* bio = tls->bio; DataChunk chunks[2]; BIO* bufferedBio = findBufferedBio(bio); @@ -399,7 +844,7 @@ int tls_write_all(rdpTls* tls, const BYTE* data, int length) do { status = BIO_write(bio, data, length); - /*fprintf(stderr, "%s: BIO_write(len=%d) = %d (retry=%d)\n", __FUNCTION__, length, status, BIO_should_retry(bio));*/ + if (status > 0) break; @@ -408,6 +853,7 @@ int tls_write_all(rdpTls* tls, const BYTE* data, int length) /* we try to handle SSL want_read and want_write nicely */ rsetPtr = wsetPtr = 0; + if (tcp->writeBlocked) { wsetPtr = &wset; @@ -431,6 +877,7 @@ int tls_write_all(rdpTls* tls, const BYTE* data, int length) tv.tv_usec = 100 * 1000; status = select(tcp->sockfd + 1, rsetPtr, wsetPtr, NULL, &tv); + if (status < 0) return -1; } @@ -447,6 +894,7 @@ int tls_write_all(rdpTls* tls, const BYTE* data, int length) while (chunks[i].size) { status = BIO_write(tcp->socketBio, chunks[i].data, chunks[i].size); + if (status > 0) { chunks[i].size -= status; @@ -457,12 +905,14 @@ int tls_write_all(rdpTls* tls, const BYTE* data, int length) if (!BIO_should_retry(tcp->socketBio)) goto out_fail; + FD_ZERO(&rset); FD_SET(tcp->sockfd, &rset); tv.tv_sec = 0; tv.tv_usec = 100 * 1000; status = select(tcp->sockfd + 1, &rset, NULL, NULL, &tv); + if (status < 0) goto out_fail; } @@ -478,8 +928,6 @@ out_fail: return -1; } - - int tls_set_alert_code(rdpTls* tls, int level, int description) { tls->alertLevel = level; diff --git a/winpr/libwinpr/winsock/winsock.c b/winpr/libwinpr/winsock/winsock.c index 3f573f9da..ac07ea694 100644 --- a/winpr/libwinpr/winsock/winsock.c +++ b/winpr/libwinpr/winsock/winsock.c @@ -255,6 +255,10 @@ PCSTR inet_ntop(INT Family, PVOID pAddr, PSTR pStringBuf, size_t StringBufSize) #include #include +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + int WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData) { ZeroMemory(lpWSAData, sizeof(WSADATA)); @@ -423,6 +427,17 @@ int WSAGetLastError(void) iError = WSAEREMOTE; break; + /* Special cases */ + +#if (EAGAIN != EWOULDBLOCK) + case EAGAIN: + iError = WSAEWOULDBLOCK; + break; +#endif + + case EPROTO: + iError = WSAECONNRESET; + break; } /** @@ -445,7 +460,7 @@ int WSAGetLastError(void) * WSAEREFUSED */ - return 0; + return iError; } SOCKET _accept(SOCKET s, struct sockaddr* addr, int* addrlen) @@ -607,6 +622,8 @@ int _send(SOCKET s, const char* buf, int len, int flags) int status; int fd = (int) s; + flags |= MSG_NOSIGNAL; + status = (int) send(fd, (void*) buf, (size_t) len, flags); return status;