rdesktop/tcp.c
Cendio ab50ea31cf Implement dynamic session resize
This adds support for resizing the RDP session dynamically based on
the window size. Some complicated logic has been added to avoid
sending excessive amounts of resize requests to the RDP server.

When supported, this resize mechanism should use the RDPEDISP way of
signalling the server to initiate a Deactivate/Activate sequence, but
rdesktop will fall back on Disconnect/Reconnect if RDPEDISP is not
supported by the server.

ui_select has been refactored and most functionality has been broken
out into three new functions, simplifying ui_select into a loop.

Signed-off-by: Henrik Andersson <hean01@cendio.com>
Signed-off-by: Karl Mikaelsson <derfian@cendio.se>
Signed-off-by: Thomas Nilefalk <thoni56@cendio.se>
2017-12-07 11:15:03 +01:00

596 lines
12 KiB
C

/* -*- c-basic-offset: 8 -*-
rdesktop: A Remote Desktop Protocol client.
Protocol services - TCP layer
Copyright (C) Matthew Chapman <matthewc.unsw.edu.au> 1999-2008
Copyright 2005-2011 Peter Astrand <astrand@cendio.se> for Cendio AB
Copyright 2012-2017 Henrik Andersson <hean01@cendio.se> for Cendio AB
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _WIN32
#include <unistd.h> /* select read write close */
#include <sys/socket.h> /* socket connect setsockopt */
#include <sys/time.h> /* timeval */
#include <netdb.h> /* gethostbyname */
#include <netinet/in.h> /* sockaddr_in */
#include <netinet/tcp.h> /* TCP_NODELAY */
#include <arpa/inet.h> /* inet_addr */
#include <errno.h> /* errno */
#endif
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/err.h>
#include "rdesktop.h"
#include "ssl.h"
#ifdef _WIN32
#define socklen_t int
#define TCP_CLOSE(_sck) closesocket(_sck)
#define TCP_STRERROR "tcp error"
#define TCP_BLOCKS (WSAGetLastError() == WSAEWOULDBLOCK)
#else
#define TCP_CLOSE(_sck) close(_sck)
#define TCP_STRERROR strerror(errno)
#define TCP_BLOCKS (errno == EWOULDBLOCK)
#endif
#ifndef INADDR_NONE
#define INADDR_NONE ((unsigned long) -1)
#endif
#ifdef WITH_SCARD
#define STREAM_COUNT 8
#else
#define STREAM_COUNT 1
#endif
static RD_BOOL g_ssl_initialized = False;
static SSL *g_ssl = NULL;
static SSL_CTX *g_ssl_ctx = NULL;
static int g_sock;
static RD_BOOL g_run_ui = False;
static struct stream g_in;
static struct stream g_out[STREAM_COUNT];
int g_tcp_port_rdp = TCP_PORT_RDP;
extern RD_BOOL g_exit_mainloop;
extern RD_BOOL g_network_error;
extern RD_BOOL g_reconnect_loop;
/* wait till socket is ready to write or timeout */
static RD_BOOL
tcp_can_send(int sck, int millis)
{
fd_set wfds;
struct timeval time;
int sel_count;
time.tv_sec = millis / 1000;
time.tv_usec = (millis * 1000) % 1000000;
FD_ZERO(&wfds);
FD_SET(sck, &wfds);
sel_count = select(sck + 1, 0, &wfds, 0, &time);
if (sel_count > 0)
{
return True;
}
return False;
}
/* Initialise TCP transport data packet */
STREAM
tcp_init(uint32 maxlen)
{
static int cur_stream_id = 0;
STREAM result = NULL;
#ifdef WITH_SCARD
scard_lock(SCARD_LOCK_TCP);
#endif
result = &g_out[cur_stream_id];
s_realloc(result, maxlen);
s_reset(result);
cur_stream_id = (cur_stream_id + 1) % STREAM_COUNT;
#ifdef WITH_SCARD
scard_unlock(SCARD_LOCK_TCP);
#endif
return result;
}
/* Send TCP transport data packet */
void
tcp_send(STREAM s)
{
int ssl_err;
int length = s->end - s->data;
int sent, total = 0;
if (g_network_error == True)
return;
#ifdef WITH_SCARD
scard_lock(SCARD_LOCK_TCP);
#endif
while (total < length)
{
if (g_ssl)
{
sent = SSL_write(g_ssl, s->data + total, length - total);
if (sent <= 0)
{
ssl_err = SSL_get_error(g_ssl, sent);
if (sent < 0 && (ssl_err == SSL_ERROR_WANT_READ ||
ssl_err == SSL_ERROR_WANT_WRITE))
{
tcp_can_send(g_sock, 100);
sent = 0;
}
else
{
#ifdef WITH_SCARD
scard_unlock(SCARD_LOCK_TCP);
#endif
logger(Core, Error,
"tcp_send(), SSL_write() failed with %d: %s",
ssl_err, TCP_STRERROR);
g_network_error = True;
return;
}
}
}
else
{
sent = send(g_sock, s->data + total, length - total, 0);
if (sent <= 0)
{
if (sent == -1 && TCP_BLOCKS)
{
tcp_can_send(g_sock, 100);
sent = 0;
}
else
{
#ifdef WITH_SCARD
scard_unlock(SCARD_LOCK_TCP);
#endif
logger(Core, Error, "tcp_send(), send() failed: %s",
TCP_STRERROR);
g_network_error = True;
return;
}
}
}
total += sent;
}
#ifdef WITH_SCARD
scard_unlock(SCARD_LOCK_TCP);
#endif
}
/* Receive a message on the TCP layer */
STREAM
tcp_recv(STREAM s, uint32 length)
{
uint32 new_length, end_offset, p_offset;
int rcvd = 0, ssl_err;
if (g_network_error == True)
return NULL;
if (s == NULL)
{
/* read into "new" stream */
if (length > g_in.size)
{
g_in.data = (uint8 *) xrealloc(g_in.data, length);
g_in.size = length;
}
g_in.end = g_in.p = g_in.data;
s = &g_in;
}
else
{
/* append to existing stream */
new_length = (s->end - s->data) + length;
if (new_length > s->size)
{
p_offset = s->p - s->data;
end_offset = s->end - s->data;
s->data = (uint8 *) xrealloc(s->data, new_length);
s->size = new_length;
s->p = s->data + p_offset;
s->end = s->data + end_offset;
}
}
while (length > 0)
{
if ((!g_ssl || SSL_pending(g_ssl) <= 0) && g_run_ui)
{
ui_select(g_sock);
/* break out of recv, if request of exiting
main loop has been done */
if (g_exit_mainloop == True)
return NULL;
}
if (g_ssl)
{
rcvd = SSL_read(g_ssl, s->end, length);
ssl_err = SSL_get_error(g_ssl, rcvd);
if (ssl_err == SSL_ERROR_SSL)
{
if (SSL_get_shutdown(g_ssl) & SSL_RECEIVED_SHUTDOWN)
{
logger(Core, Error,
"tcp_recv(), remote peer initiated ssl shutdown");
return NULL;
}
rdssl_log_ssl_errors("tcp_recv()");
g_network_error = True;
return NULL;
}
if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE)
{
rcvd = 0;
}
else if (ssl_err != SSL_ERROR_NONE)
{
logger(Core, Error, "tcp_recv(), SSL_read() failed with %d: %s",
ssl_err, TCP_STRERROR);
g_network_error = True;
return NULL;
}
}
else
{
rcvd = recv(g_sock, s->end, length, 0);
if (rcvd < 0)
{
if (rcvd == -1 && TCP_BLOCKS)
{
rcvd = 0;
}
else
{
logger(Core, Error, "tcp_recv(), recv() failed: %s",
TCP_STRERROR);
g_network_error = True;
return NULL;
}
}
else if (rcvd == 0)
{
logger(Core, Error, "rcp_recv(), connection closed by peer");
return NULL;
}
}
s->end += rcvd;
length -= rcvd;
}
return s;
}
/* Establish a SSL/TLS 1.0 connection */
RD_BOOL
tcp_tls_connect(void)
{
int err;
long options;
if (!g_ssl_initialized)
{
SSL_load_error_strings();
SSL_library_init();
g_ssl_initialized = True;
}
/* create process context */
if (g_ssl_ctx == NULL)
{
g_ssl_ctx = SSL_CTX_new(TLSv1_client_method());
if (g_ssl_ctx == NULL)
{
logger(Core, Error,
"tcp_tls_connect(), SSL_CTX_new() failed to create TLS v1.0 context\n");
goto fail;
}
options = 0;
#ifdef SSL_OP_NO_COMPRESSION
options |= SSL_OP_NO_COMPRESSION;
#endif // __SSL_OP_NO_COMPRESSION
options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
SSL_CTX_set_options(g_ssl_ctx, options);
}
/* free old connection */
if (g_ssl)
SSL_free(g_ssl);
/* create new ssl connection */
g_ssl = SSL_new(g_ssl_ctx);
if (g_ssl == NULL)
{
logger(Core, Error, "tcp_tls_connect(), SSL_new() failed");
goto fail;
}
if (SSL_set_fd(g_ssl, g_sock) < 1)
{
logger(Core, Error, "tcp_tls_connect(), SSL_set_fd() failed");
goto fail;
}
do
{
err = SSL_connect(g_ssl);
}
while (SSL_get_error(g_ssl, err) == SSL_ERROR_WANT_READ);
if (err < 0)
{
rdssl_log_ssl_errors("tcp_tls_connect()");
goto fail;
}
return True;
fail:
if (g_ssl)
SSL_free(g_ssl);
if (g_ssl_ctx)
SSL_CTX_free(g_ssl_ctx);
g_ssl = NULL;
g_ssl_ctx = NULL;
return False;
}
/* Get public key from server of TLS 1.0 connection */
RD_BOOL
tcp_tls_get_server_pubkey(STREAM s)
{
X509 *cert = NULL;
EVP_PKEY *pkey = NULL;
s->data = s->p = NULL;
s->size = 0;
if (g_ssl == NULL)
goto out;
cert = SSL_get_peer_certificate(g_ssl);
if (cert == NULL)
{
logger(Core, Error,
"tcp_tls_get_server_pubkey(), SSL_get_peer_certificate() failed");
goto out;
}
pkey = X509_get_pubkey(cert);
if (pkey == NULL)
{
logger(Core, Error, "tcp_tls_get_server_pubkey(), X509_get_pubkey() failed");
goto out;
}
s->size = i2d_PublicKey(pkey, NULL);
if (s->size < 1)
{
logger(Core, Error, "tcp_tls_get_server_pubkey(), i2d_PublicKey() failed");
goto out;
}
s->data = s->p = xmalloc(s->size);
i2d_PublicKey(pkey, &s->p);
s->p = s->data;
s->end = s->p + s->size;
out:
if (cert)
X509_free(cert);
if (pkey)
EVP_PKEY_free(pkey);
return (s->size != 0);
}
/* Establish a connection on the TCP layer */
RD_BOOL
tcp_connect(char *server)
{
socklen_t option_len;
uint32 option_value;
int i;
#ifdef IPv6
int n;
struct addrinfo hints, *res, *ressave;
char tcp_port_rdp_s[10];
snprintf(tcp_port_rdp_s, 10, "%d", g_tcp_port_rdp);
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((n = getaddrinfo(server, tcp_port_rdp_s, &hints, &res)))
{
logger(Core, Error, "tcp_connect(), getaddrinfo() failed: %s", gai_strerror(n));
return False;
}
ressave = res;
g_sock = -1;
while (res)
{
g_sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (!(g_sock < 0))
{
if (connect(g_sock, res->ai_addr, res->ai_addrlen) == 0)
break;
TCP_CLOSE(g_sock);
g_sock = -1;
}
res = res->ai_next;
}
freeaddrinfo(ressave);
if (g_sock == -1)
{
logger(Core, Error, "tcp_connect(), unable to connect to %s", server);
return False;
}
#else /* no IPv6 support */
struct hostent *nslookup;
struct sockaddr_in servaddr;
if ((nslookup = gethostbyname(server)) != NULL)
{
memcpy(&servaddr.sin_addr, nslookup->h_addr, sizeof(servaddr.sin_addr));
}
else if ((servaddr.sin_addr.s_addr = inet_addr(server)) == INADDR_NONE)
{
logger(Core, Error, "tcp_connect(), unable to resolve host '%s'", server);
return False;
}
if ((g_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
logger(Core, Error, "tcp_connect(), socket() failed: %s", TCP_STRERROR);
return False;
}
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons((uint16) g_tcp_port_rdp);
if (connect(g_sock, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) < 0)
{
if (!g_reconnect_loop)
logger(Core, Error, "tcp_connect(), connect() failed: %s", TCP_STRERROR);
TCP_CLOSE(g_sock);
g_sock = -1;
return False;
}
#endif /* IPv6 */
option_value = 1;
option_len = sizeof(option_value);
setsockopt(g_sock, IPPROTO_TCP, TCP_NODELAY, (void *) &option_value, option_len);
/* receive buffer must be a least 16 K */
if (getsockopt(g_sock, SOL_SOCKET, SO_RCVBUF, (void *) &option_value, &option_len) == 0)
{
if (option_value < (1024 * 16))
{
option_value = 1024 * 16;
option_len = sizeof(option_value);
setsockopt(g_sock, SOL_SOCKET, SO_RCVBUF, (void *) &option_value,
option_len);
}
}
g_in.size = 4096;
g_in.data = (uint8 *) xmalloc(g_in.size);
for (i = 0; i < STREAM_COUNT; i++)
{
g_out[i].size = 4096;
g_out[i].data = (uint8 *) xmalloc(g_out[i].size);
}
return True;
}
/* Disconnect on the TCP layer */
void
tcp_disconnect(void)
{
if (g_ssl)
{
if (!g_network_error)
(void) SSL_shutdown(g_ssl);
SSL_free(g_ssl);
g_ssl = NULL;
SSL_CTX_free(g_ssl_ctx);
g_ssl_ctx = NULL;
}
TCP_CLOSE(g_sock);
g_sock = -1;
}
char *
tcp_get_address()
{
static char ipaddr[32];
struct sockaddr_in sockaddr;
socklen_t len = sizeof(sockaddr);
if (getsockname(g_sock, (struct sockaddr *) &sockaddr, &len) == 0)
{
uint8 *ip = (uint8 *) & sockaddr.sin_addr;
sprintf(ipaddr, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
}
else
strcpy(ipaddr, "127.0.0.1");
return ipaddr;
}
RD_BOOL
tcp_is_connected()
{
struct sockaddr_in sockaddr;
socklen_t len = sizeof(sockaddr);
if (getpeername(g_sock, (struct sockaddr *) &sockaddr, &len))
return True;
return False;
}
/* reset the state of the tcp layer */
/* Support for Session Directory */
void
tcp_reset_state(void)
{
int i;
/* Clear the incoming stream */
s_reset(&g_in);
/* Clear the outgoing stream(s) */
for (i = 0; i < STREAM_COUNT; i++)
{
s_reset(&g_out[i]);
}
}
void
tcp_run_ui(RD_BOOL run)
{
g_run_ui = run;
}