From c44025aa182285bce0ea9ca8e5ba10cd0756fa54 Mon Sep 17 00:00:00 2001 From: Henrik Andersson Date: Fri, 15 Jun 2012 05:16:20 +0000 Subject: [PATCH] Added support for protocol negotiation, this is a part of adding Enhanced RDP Security support to rdesktop and brings support for TLSv1 tunnel functionality. git-svn-id: svn://svn.code.sf.net/p/rdesktop/code/rdesktop/trunk@1659 423420c4-83ab-492f-b58f-81f9feb106b5 --- channels.c | 4 +- configure.ac | 4 +- constants.h | 25 +++++++ iso.c | 116 +++++++++++++++++++++++++++-- licence.c | 10 ++- mcs.c | 11 ++- orders.c | 4 +- proto.h | 6 +- rdesktop.c | 8 +- rdp.c | 24 ++++-- secure.c | 133 +++++++++++++++++++-------------- tcp.c | 202 ++++++++++++++++++++++++++++++++++++++++++++++----- types.h | 9 +++ xkeymap.c | 8 +- 14 files changed, 455 insertions(+), 109 deletions(-) diff --git a/channels.c b/channels.c index 18dce63..42328f2 100644 --- a/channels.c +++ b/channels.c @@ -26,7 +26,7 @@ #define CHANNEL_FLAG_LAST 0x02 #define CHANNEL_FLAG_SHOW_PROTOCOL 0x10 -extern RD_BOOL g_use_rdp5; +extern RDP_VERSION g_rdp_version; extern RD_BOOL g_encryption; VCHANNEL g_channels[MAX_CHANNELS]; @@ -47,7 +47,7 @@ channel_register(char *name, uint32 flags, void (*callback) (STREAM)) { VCHANNEL *channel; - if (!g_use_rdp5) + if (g_rdp_version < RDP_V5) return NULL; if (g_num_channels >= MAX_CHANNELS) diff --git a/configure.ac b/configure.ac index 76eb216..124f2bf 100644 --- a/configure.ac +++ b/configure.ac @@ -103,9 +103,9 @@ AC_ARG_ENABLE(static-openssl, if test x"$static_openssl" = "xyes"; then # OpenSSL generally relies on libz AC_SEARCH_LIBS(deflate, z) - LIBS="-L$ssldir/lib -L$ssldir/lib64 -Wl,-Bstatic -lcrypto -Wl,-Bdynamic $LIBS" + LIBS="-L$ssldir/lib -L$ssldir/lib64 -Wl,-Bstatic -lcrypto -lssl -Wl,-Bdynamic $LIBS" else - LIBS="-L$ssldir/lib -L$ssldir/lib64 -lcrypto $LIBS" + LIBS="-L$ssldir/lib -L$ssldir/lib64 -lcrypto -lssl $LIBS" # # target-specific stuff diff --git a/constants.h b/constants.h index 4999bf0..1832bd2 100644 --- a/constants.h +++ b/constants.h @@ -33,6 +33,31 @@ enum ISO_PDU_CODE ISO_PDU_ER = 0x70 /* Error */ }; +/* RDP protocol negotiating constants */ +enum RDP_NEG_TYPE_CODE +{ + RDP_NEG_REQ = 1, + RDP_NEG_RSP = 2, + RDP_NEG_FAILURE = 3 +}; + +enum RDP_NEG_REQ_CODE +{ + PROTOCOL_RDP = 0, + PROTOCOL_SSL = 1, + PROTOCOL_HYBRID = 2 +}; + +enum RDP_NEG_FAILURE_CODE +{ + SSL_REQUIRED_BY_SERVER = 1, + SSL_NOT_ALLOWED_BY_SERVER = 2, + SSL_CERT_NOT_ON_SERVER = 3, + INCONSISTENT_FLAGS = 4, + HYBRID_REQUIRED_BY_SERVER = 5, + SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 6 +}; + /* MCS PDU codes */ enum MCS_PDU_TYPE { diff --git a/iso.c b/iso.c index 60e5db9..172e233 100644 --- a/iso.c +++ b/iso.c @@ -20,6 +20,11 @@ #include "rdesktop.h" +extern RD_BOOL g_encryption; +extern RDP_VERSION g_rdp_version; + +static RD_BOOL g_negotiate_rdp_protocol = True; + /* Send a self-contained ISO PDU */ static void iso_send_msg(uint8 code) @@ -48,6 +53,9 @@ iso_send_connection_request(char *username) STREAM s; int length = 30 + strlen(username); + if (g_rdp_version >= RDP_V5 && g_negotiate_rdp_protocol) + length += 8; + s = tcp_init(length); out_uint8(s, 3); /* version */ @@ -63,8 +71,17 @@ iso_send_connection_request(char *username) out_uint8p(s, "Cookie: mstshash=", strlen("Cookie: mstshash=")); out_uint8p(s, username, strlen(username)); - out_uint8(s, 0x0d); /* Unknown */ - out_uint8(s, 0x0a); /* Unknown */ + out_uint8(s, 0x0d); /* cookie termination string: CR+LF */ + out_uint8(s, 0x0a); + + if (g_rdp_version >= RDP_V5 && g_negotiate_rdp_protocol) + { + /* optional rdp protocol negotiation request for RDPv5 */ + out_uint8(s, RDP_NEG_REQ); + out_uint8(s, 0); + out_uint16(s, 8); + out_uint32(s, PROTOCOL_SSL); + } s_mark_end(s); tcp_send(s); @@ -174,9 +191,16 @@ iso_recv(uint8 * rdpver) /* Establish a connection up to the ISO layer */ RD_BOOL -iso_connect(char *server, char *username, RD_BOOL reconnect) +iso_connect(char *server, char *username, RD_BOOL reconnect, uint32 * selected_protocol) { - uint8 code = 0; + STREAM s; + uint8 code; + + g_negotiate_rdp_protocol = True; + + retry: + *selected_protocol = PROTOCOL_RDP; + code = 0; if (!tcp_connect(server)) return False; @@ -190,7 +214,8 @@ iso_connect(char *server, char *username, RD_BOOL reconnect) iso_send_connection_request(username); } - if (iso_recv_msg(&code, NULL) == NULL) + s = iso_recv_msg(&code, NULL); + if (s == NULL) return False; if (code != ISO_PDU_CC) @@ -200,6 +225,87 @@ iso_connect(char *server, char *username, RD_BOOL reconnect) return False; } + if (g_rdp_version >= RDP_V5 && s_check_rem(s, 8)) + { + /* handle RDP_NEG_REQ response */ + const char *reason = NULL; + + uint8 type = 0, flags = 0; + uint16 length = 0; + uint32 data = 0; + + in_uint8(s, type); + in_uint8(s, flags); + in_uint16(s, length); + in_uint32(s, data); + + if (type == RDP_NEG_FAILURE) + { + switch (data) + { + case SSL_REQUIRED_BY_SERVER: + reason = "SSL required by server"; + break; + case SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER: + reason = "SSL with user authentication required by server"; + break; + case SSL_NOT_ALLOWED_BY_SERVER: + reason = "SSL not allowed by server"; + break; + case SSL_CERT_NOT_ON_SERVER: + reason = "SSL certificated not on server"; + break; + case INCONSISTENT_FLAGS: + reason = "inconsistent flags"; + break; + case HYBRID_REQUIRED_BY_SERVER: + reason = "hybrid authentication (CredSSP) required by server"; + break; + default: + reason = "unknown reason"; + } + + tcp_disconnect(); + warning("RDP protocol negotiation failed with reason: %s (error 0x%x),\n", + reason, data); + warning("retrying without negotiation using plain RDP protocol.\n"); + + g_negotiate_rdp_protocol = False; + goto retry; + } + + if (type != RDP_NEG_RSP) + { + tcp_disconnect(); + error("expected RDP_NEG_RSP, got type = 0x%x\n", type); + warning("retrying without negotiation using plain RDP protocol.\n"); + + g_negotiate_rdp_protocol = False; + goto retry; + } + + /* handle negotiation response */ + if (data == PROTOCOL_SSL) + { + if (!tcp_tls_connect()) + { + tcp_disconnect(); + return False; + } + + /* do not use encryption when using TLS */ + g_encryption = False; + } + else if (data != PROTOCOL_RDP) + { + tcp_disconnect(); + error("unexpected protocol in neqotiation response, got data = 0x%x.\n", + data); + return False; + } + + *selected_protocol = data; + } return True; } diff --git a/licence.c b/licence.c index 7e9190e..74bf556 100644 --- a/licence.c +++ b/licence.c @@ -23,12 +23,13 @@ extern char *g_username; extern char g_hostname[16]; -extern RD_BOOL g_use_rdp5; +extern RDP_VERSION g_rdp_version; static uint8 g_licence_key[16]; static uint8 g_licence_sign_key[16]; RD_BOOL g_licence_issued = False; +RD_BOOL g_licence_error_result = False; /* Generate a session key and RC4 keys, given client and server randoms */ static void @@ -69,7 +70,7 @@ licence_present(uint8 * client_random, uint8 * rsa_data, s = sec_init(sec_flags, length + 2); out_uint8(s, LICENCE_TAG_PRESENT); - out_uint8(s, (g_use_rdp5 ? 3 : 2)); /* version */ + out_uint8(s, ((g_rdp_version >= RDP_V5) ? 3 : 2)); /* version */ out_uint16_le(s, length); out_uint32_le(s, 1); @@ -110,7 +111,7 @@ licence_send_request(uint8 * client_random, uint8 * rsa_data, char *user, char * s = sec_init(sec_flags, length + 2); out_uint8(s, LICENCE_TAG_REQUEST); - out_uint8(s, (g_use_rdp5 ? 3 : 2)); /* version */ + out_uint8(s, ((g_rdp_version >= RDP_V5) ? 3 : 2)); /* version */ out_uint16_le(s, length); out_uint32_le(s, 1); @@ -192,7 +193,7 @@ licence_send_authresp(uint8 * token, uint8 * crypt_hwid, uint8 * signature) s = sec_init(sec_flags, length + 2); out_uint8(s, LICENCE_TAG_AUTHRESP); - out_uint8(s, (g_use_rdp5 ? 3 : 2)); /* version */ + out_uint8(s, ((g_rdp_version >= RDP_V5) ? 3 : 2)); /* version */ out_uint16_le(s, length); out_uint16_le(s, 1); @@ -333,6 +334,7 @@ licence_process(STREAM s) break; case LICENCE_TAG_RESULT: + g_licence_error_result = True; break; default: diff --git a/mcs.c b/mcs.c index fea617c..6094d7d 100644 --- a/mcs.c +++ b/mcs.c @@ -373,13 +373,16 @@ mcs_recv(uint16 * channel, uint8 * rdpver) } RD_BOOL -mcs_connect(char *server, STREAM mcs_data, char *username, RD_BOOL reconnect) +mcs_connect_start(char *server, char *username, RD_BOOL reconnect, uint32 * selected_protocol) +{ + return iso_connect(server, username, reconnect, selected_protocol); +} + +RD_BOOL +mcs_connect_finalize(STREAM mcs_data) { unsigned int i; - if (!iso_connect(server, username, reconnect)) - return False; - mcs_send_connect_initial(mcs_data); if (!mcs_recv_connect_response(mcs_data)) goto error; diff --git a/orders.c b/orders.c index eb00ac4..2185d06 100644 --- a/orders.c +++ b/orders.c @@ -22,7 +22,7 @@ extern uint8 *g_next_packet; static RDP_ORDER_STATE g_order_state; -extern RD_BOOL g_use_rdp5; +extern RDP_VERSION g_rdp_version; /* Read field indicating which parameters are present */ static void @@ -968,7 +968,7 @@ process_bmpcache(STREAM s) in_uint16_le(s, bufsize); /* bufsize */ in_uint16_le(s, cache_idx); - if (g_use_rdp5) + if (g_rdp_version >= RDP_V5) { size = bufsize; } diff --git a/proto.h b/proto.h index d5e7e82..44246c9 100644 --- a/proto.h +++ b/proto.h @@ -74,7 +74,7 @@ void ewmh_init(void); STREAM iso_init(int length); void iso_send(STREAM s); STREAM iso_recv(uint8 * rdpver); -RD_BOOL iso_connect(char *server, char *username, RD_BOOL reconnect); +RD_BOOL iso_connect(char *server, char *username, RD_BOOL reconnect, uint32 * selected_protocol); void iso_disconnect(void); void iso_reset_state(void); /* licence.c */ @@ -84,7 +84,9 @@ STREAM mcs_init(int length); void mcs_send_to_channel(STREAM s, uint16 channel); void mcs_send(STREAM s); STREAM mcs_recv(uint16 * channel, uint8 * rdpver); -RD_BOOL mcs_connect(char *server, STREAM mcs_data, char *username, RD_BOOL reconnect); +RD_BOOL mcs_connect_start(char *server, char *username, RD_BOOL reconnect, + uint32 * selected_protocol); +RD_BOOL mcs_connect_finalize(STREAM s); void mcs_disconnect(void); void mcs_reset_state(void); /* orders.c */ diff --git a/rdesktop.c b/rdesktop.c index 70979fc..93a7fce 100644 --- a/rdesktop.c +++ b/rdesktop.c @@ -92,7 +92,7 @@ RD_BOOL g_polygon_ellipse_orders = True; /* polygon / ellipse orders */ RD_BOOL g_fullscreen = False; RD_BOOL g_grab_keyboard = True; RD_BOOL g_hide_decorations = False; -RD_BOOL g_use_rdp5 = True; +RDP_VERSION g_rdp_version = RDP_V5; /* Default to version 5 */ RD_BOOL g_rdpclip = True; RD_BOOL g_console_session = False; RD_BOOL g_numlock_sync = False; @@ -838,11 +838,11 @@ main(int argc, char *argv[]) break; case '4': - g_use_rdp5 = False; + g_rdp_version = RDP_V4; break; case '5': - g_use_rdp5 = True; + g_rdp_version = RDP_V5; break; case 'h': @@ -890,7 +890,7 @@ main(int argc, char *argv[]) error("You cannot use -X and -A at the same time\n"); return EX_USAGE; } - if (!g_use_rdp5) + if (g_rdp_version < RDP_V5) { error("You cannot use -4 and -A at the same time\n"); return EX_USAGE; diff --git a/rdp.c b/rdp.c index 3fb24df..872ec13 100644 --- a/rdp.c +++ b/rdp.c @@ -44,7 +44,7 @@ extern RD_BOOL g_orders; extern RD_BOOL g_encryption; extern RD_BOOL g_desktop_save; extern RD_BOOL g_polygon_ellipse_orders; -extern RD_BOOL g_use_rdp5; +extern RDP_VERSION g_rdp_version; extern uint16 g_server_rdp_version; extern uint32 g_rdp5_performanceflags; extern int g_server_depth; @@ -341,7 +341,7 @@ rdp_send_logon_info(uint32 flags, char *domain, char *user, time_t tzone; uint8 security_verifier[16]; - if (!g_use_rdp5 || 1 == g_server_rdp_version) + if (g_rdp_version == RDP_V4 || 1 == g_server_rdp_version) { DEBUG_RDP5(("Sending RDP4-style Logon packet\n")); @@ -644,7 +644,7 @@ rdp_out_general_caps(STREAM s) out_uint16_le(s, 0x200); /* Protocol version */ out_uint16(s, 0); /* Pad */ out_uint16(s, 0); /* Compression types */ - out_uint16_le(s, g_use_rdp5 ? 0x40d : 0); + out_uint16_le(s, (g_rdp_version >= RDP_V5) ? 0x40d : 0); /* Pad, according to T.128. 0x40d seems to trigger the server to start sending RDP5 packets. @@ -896,7 +896,7 @@ rdp_send_confirm_active(void) RDP_CAPLEN_BRUSHCACHE + 0x58 + 0x08 + 0x08 + 0x34 /* unknown caps */ + 4 /* w2k fix, sessionid */ ; - if (g_use_rdp5) + if (g_rdp_version >= RDP_V5) { caplen += RDP_CAPLEN_BMPCACHE2; caplen += RDP_CAPLEN_NEWPOINTER; @@ -925,7 +925,7 @@ rdp_send_confirm_active(void) rdp_out_general_caps(s); rdp_out_bitmap_caps(s); rdp_out_order_caps(s); - if (g_use_rdp5) + if (g_rdp_version >= RDP_V5) { rdp_out_bmpcache2_caps(s); rdp_out_newpointer_caps(s); @@ -960,7 +960,7 @@ rdp_process_general_caps(STREAM s) in_uint16_le(s, pad2octetsB); if (!pad2octetsB) - g_use_rdp5 = False; + g_rdp_version = RDP_V4; } /* Process a bitmap capability set */ @@ -1060,7 +1060,7 @@ process_demand_active(STREAM s) rdp_send_input(0, RDP_INPUT_SYNCHRONIZE, 0, g_numlock_sync ? ui_get_numlock_state(read_keyboard_state()) : 0, 0); - if (g_use_rdp5) + if (g_rdp_version >= RDP_V5) { rdp_enum_bmpcache2(); rdp_send_fonts(3); @@ -1633,10 +1633,20 @@ RD_BOOL rdp_connect(char *server, uint32 flags, char *domain, char *password, char *command, char *directory, RD_BOOL reconnect) { + RD_BOOL deactivated = False; + uint32 ext_disc_reason = 0; + if (!sec_connect(server, g_username, reconnect)) return False; rdp_send_logon_info(flags, domain, g_username, password, command, directory); + + /* run RDP loop until first licence demand active PDU */ + while (!g_rdp_shareid) + { + if (!rdp_loop(&deactivated, &ext_disc_reason)) + return False; + } return True; } diff --git a/secure.c b/secure.c index 8ad565d..5dc8417 100644 --- a/secure.c +++ b/secure.c @@ -30,7 +30,8 @@ extern int g_keyboard_subtype; extern int g_keyboard_functionkeys; extern RD_BOOL g_encryption; extern RD_BOOL g_licence_issued; -extern RD_BOOL g_use_rdp5; +extern RD_BOOL g_licence_error_result; +extern RDP_VERSION g_rdp_version; extern RD_BOOL g_console_session; extern int g_server_depth; extern VCHANNEL g_channels[]; @@ -316,7 +317,7 @@ sec_init(uint32 flags, int maxlen) int hdrlen; STREAM s; - if (!g_licence_issued) + if (!g_licence_issued && !g_licence_error_result) hdrlen = (flags & SEC_ENCRYPT) ? 12 : 4; else hdrlen = (flags & SEC_ENCRYPT) ? 12 : 0; @@ -337,7 +338,7 @@ sec_send_to_channel(STREAM s, uint32 flags, uint16 channel) #endif s_pop_layer(s, sec_hdr); - if (!g_licence_issued || (flags & SEC_ENCRYPT)) + if ((!g_licence_issued && !g_licence_error_result) || (flags & SEC_ENCRYPT)) out_uint32_le(s, flags); if (flags & SEC_ENCRYPT) @@ -390,10 +391,10 @@ sec_establish_key(void) /* Output connect initial data blob */ static void -sec_out_mcs_data(STREAM s) +sec_out_mcs_data(STREAM s, uint32 selected_protocol) { int hostlen = 2 * strlen(g_hostname); - int length = 158 + 76 + 12 + 4; + int length = 162 + 76 + 12 + 4; unsigned int i; if (g_num_channels > 0) @@ -421,8 +422,8 @@ sec_out_mcs_data(STREAM s) /* Client information */ out_uint16_le(s, SEC_TAG_CLI_INFO); - out_uint16_le(s, 212); /* length */ - out_uint16_le(s, g_use_rdp5 ? 4 : 1); /* RDP version. 1 == RDP4, 4 == RDP5. */ + out_uint16_le(s, 216); /* length */ + out_uint16_le(s, (g_rdp_version >= RDP_V5) ? 4 : 1); /* RDP version. 1 == RDP4, 4 >= RDP5 to RDP8 */ out_uint16_le(s, 8); out_uint16_le(s, g_width); out_uint16_le(s, g_height); @@ -449,7 +450,8 @@ sec_out_mcs_data(STREAM s) out_uint16_le(s, 0x0700); out_uint8(s, 0); out_uint32_le(s, 1); - out_uint8s(s, 64); /* End of client info */ + out_uint8s(s, 64); + out_uint32_le(s, selected_protocol); /* End of client info */ out_uint16_le(s, SEC_TAG_CLI_4); out_uint16_le(s, 12); @@ -541,8 +543,12 @@ sec_parse_crypt_info(STREAM s, uint32 * rc4_key_size, in_uint32_le(s, *rc4_key_size); /* 1 = 40-bit, 2 = 128-bit */ in_uint32_le(s, crypt_level); /* 1 = low, 2 = medium, 3 = high */ - if (crypt_level == 0) /* no encryption */ + if (crypt_level == 0) + { + /* no encryption */ return False; + } + in_uint32_le(s, random_len); in_uint32_le(s, rsa_info_len); @@ -721,7 +727,7 @@ sec_process_srv_info(STREAM s) DEBUG_RDP5(("Server RDP version is %d\n", g_server_rdp_version)); if (1 == g_server_rdp_version) { - g_use_rdp5 = 0; + g_rdp_version = RDP_V4; g_server_depth = 8; } } @@ -796,56 +802,67 @@ sec_recv(uint8 * rdpver) return s; } } - if (g_encryption || !g_licence_issued) + if (g_encryption || (!g_licence_issued && !g_licence_error_result)) { in_uint32_le(s, sec_flags); - if (sec_flags & SEC_ENCRYPT) + if (g_encryption) { - in_uint8s(s, 8); /* signature */ - sec_decrypt(s->p, s->end - s->p); - } - - if (sec_flags & SEC_LICENCE_NEG) - { - licence_process(s); - continue; - } - - if (sec_flags & 0x0400) /* SEC_REDIRECT_ENCRYPT */ - { - uint8 swapbyte; - - in_uint8s(s, 8); /* signature */ - sec_decrypt(s->p, s->end - s->p); - - /* Check for a redirect packet, starts with 00 04 */ - if (s->p[0] == 0 && s->p[1] == 4) + if (sec_flags & SEC_ENCRYPT) { - /* for some reason the PDU and the length seem to be swapped. - This isn't good, but we're going to do a byte for byte - swap. So the first foure value appear as: 00 04 XX YY, - where XX YY is the little endian length. We're going to - use 04 00 as the PDU type, so after our swap this will look - like: XX YY 04 00 */ - swapbyte = s->p[0]; - s->p[0] = s->p[2]; - s->p[2] = swapbyte; - - swapbyte = s->p[1]; - s->p[1] = s->p[3]; - s->p[3] = swapbyte; - - swapbyte = s->p[2]; - s->p[2] = s->p[3]; - s->p[3] = swapbyte; + in_uint8s(s, 8); /* signature */ + sec_decrypt(s->p, s->end - s->p); } -#ifdef WITH_DEBUG - /* warning! this debug statement will show passwords in the clear! */ - hexdump(s->p, s->end - s->p); -#endif - } + if (sec_flags & SEC_LICENCE_NEG) + { + licence_process(s); + continue; + } + + if (sec_flags & 0x0400) /* SEC_REDIRECT_ENCRYPT */ + { + uint8 swapbyte; + + in_uint8s(s, 8); /* signature */ + sec_decrypt(s->p, s->end - s->p); + + /* Check for a redirect packet, starts with 00 04 */ + if (s->p[0] == 0 && s->p[1] == 4) + { + /* for some reason the PDU and the length seem to be swapped. + This isn't good, but we're going to do a byte for byte + swap. So the first foure value appear as: 00 04 XX YY, + where XX YY is the little endian length. We're going to + use 04 00 as the PDU type, so after our swap this will look + like: XX YY 04 00 */ + swapbyte = s->p[0]; + s->p[0] = s->p[2]; + s->p[2] = swapbyte; + + swapbyte = s->p[1]; + s->p[1] = s->p[3]; + s->p[3] = swapbyte; + + swapbyte = s->p[2]; + s->p[2] = s->p[3]; + s->p[3] = swapbyte; + } +#ifdef WITH_DEBUG + /* warning! this debug statement will show passwords in the clear! */ + hexdump(s->p, s->end - s->p); +#endif + } + } + else + { + if ((sec_flags & 0xffff) == SEC_LICENCE_NEG) + { + licence_process(s); + continue; + } + s->p -= 4; + } } if (channel != MCS_GLOBAL_CHANNEL) @@ -866,14 +883,20 @@ sec_recv(uint8 * rdpver) RD_BOOL sec_connect(char *server, char *username, RD_BOOL reconnect) { + uint32 selected_proto; struct stream mcs_data; + /* Start a MCS connect sequence */ + if (!mcs_connect_start(server, username, reconnect, &selected_proto)) + return False; + /* We exchange some RDP data during the MCS-Connect */ mcs_data.size = 512; mcs_data.p = mcs_data.data = (uint8 *) xmalloc(mcs_data.size); - sec_out_mcs_data(&mcs_data); + sec_out_mcs_data(&mcs_data, selected_proto); - if (!mcs_connect(server, &mcs_data, username, reconnect)) + /* finialize the MCS connect sequence */ + if (!mcs_connect_finalize(&mcs_data)) return False; /* sec_process_mcs_data(&mcs_data); */ diff --git a/tcp.c b/tcp.c index 6992e8f..1fa3a00 100644 --- a/tcp.c +++ b/tcp.c @@ -29,6 +29,10 @@ #include /* errno */ #endif +#include +#include +#include + #include "rdesktop.h" #ifdef _WIN32 @@ -52,6 +56,8 @@ #define STREAM_COUNT 1 #endif +static SSL *g_ssl = NULL; +static SSL_CTX *g_ssl_ctx = NULL; static int g_sock; static struct stream g_in; static struct stream g_out[STREAM_COUNT]; @@ -109,6 +115,7 @@ tcp_init(uint32 maxlen) void tcp_send(STREAM s) { + int ssl_err; int length = s->end - s->data; int sent, total = 0; @@ -117,18 +124,40 @@ tcp_send(STREAM s) #endif while (total < length) { - sent = send(g_sock, s->data + total, length - total, 0); - if (sent <= 0) + if (g_ssl) { - if (sent == -1 && TCP_BLOCKS) + sent = SSL_write(g_ssl, s->data + total, length - total); + if (sent <= 0) { - tcp_can_send(g_sock, 100); - 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 + { + error("SSL_write: %d (%s)\n", ssl_err, TCP_STRERROR); + return; + } } - else + } + else + { + sent = send(g_sock, s->data + total, length - total, 0); + if (sent <= 0) { - error("send: %s\n", TCP_STRERROR); - return; + if (sent == -1 && TCP_BLOCKS) + { + tcp_can_send(g_sock, 100); + sent = 0; + } + else + { + error("send: %s\n", TCP_STRERROR); + return; + } } } total += sent; @@ -143,7 +172,7 @@ STREAM tcp_recv(STREAM s, uint32 length) { uint32 new_length, end_offset, p_offset; - int rcvd = 0; + int rcvd = 0, ssl_err; if (s == NULL) { @@ -173,30 +202,55 @@ tcp_recv(STREAM s, uint32 length) while (length > 0) { - if (!ui_select(g_sock)) + if ((!g_ssl || SSL_pending(g_ssl) <= 0) && !ui_select(g_sock)) { /* User quit */ g_user_quit = True; return NULL; } - rcvd = recv(g_sock, s->end, length, 0); - if (rcvd < 0) + if (g_ssl) { - if (rcvd == -1 && TCP_BLOCKS) + rcvd = SSL_read(g_ssl, s->end, length); + ssl_err = SSL_get_error(g_ssl, rcvd); + + if (ssl_err == SSL_ERROR_SSL) + { + ERR_print_errors_fp(stdout); + return NULL; + } + + if (ssl_err == SSL_ERROR_WANT_READ || ssl_err == SSL_ERROR_WANT_WRITE) { rcvd = 0; } - else + else if (ssl_err != SSL_ERROR_NONE) { - error("recv: %s\n", TCP_STRERROR); + error("SSL_read: %d (%s)\n", ssl_err, TCP_STRERROR); return NULL; } + } - else if (rcvd == 0) + else { - error("Connection closed\n"); - return NULL; + rcvd = recv(g_sock, s->end, length, 0); + if (rcvd < 0) + { + if (rcvd == -1 && TCP_BLOCKS) + { + rcvd = 0; + } + else + { + error("recv: %s\n", TCP_STRERROR); + return NULL; + } + } + else if (rcvd == 0) + { + error("Connection closed\n"); + return NULL; + } } s->end += rcvd; @@ -206,6 +260,108 @@ tcp_recv(STREAM s, uint32 length) return s; } +/* Establish a SSL/TLS 1.0 connection */ +RD_BOOL +tcp_tls_connect(void) +{ + int err; + + SSL_load_error_strings(); + SSL_library_init(); + + g_ssl_ctx = SSL_CTX_new(TLSv1_client_method()); + if (g_ssl_ctx == NULL) + { + error("tcp_tls_connect: SSL_CTX_new() failed to create TLS v1.0 context\n"); + goto fail; + } + + SSL_CTX_set_options(g_ssl_ctx, SSL_OP_ALL); + + g_ssl = SSL_new(g_ssl_ctx); + if (g_ssl == NULL) + { + error("tcp_tls_connect: SSL_new() failed\n"); + goto fail; + } + + if (SSL_set_fd(g_ssl, g_sock) < 1) + { + error("tcp_tls_connect: SSL_set_fd() failed\n"); + goto fail; + } + + do + { + err = SSL_connect(g_ssl); + } + while (SSL_get_error(g_ssl, err) == SSL_ERROR_WANT_READ); + + if (err < 0) + { + ERR_print_errors_fp(stdout); + 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) + { + error("tcp_tls_get_server_pubkey: SSL_get_peer_certificate() failed\n"); + goto out; + } + + pkey = X509_get_pubkey(cert); + if (pkey == NULL) + { + error("tcp_tls_get_server_pubkey: X509_get_pubkey() failed\n"); + goto out; + } + + s->size = i2d_PublicKey(pkey, NULL); + if (s->size < 1) + { + error("tcp_tls_get_server_pubkey: i2d_PublicKey() failed\n"); + goto out; + } + + s->data = s->p = xmalloc(s->size + 1); + i2d_PublicKey(pkey, &s->p); + s->end = s->p; + + 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) @@ -318,6 +474,16 @@ tcp_connect(char *server) void tcp_disconnect(void) { + int err; + if (g_ssl) + { + err = 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); } diff --git a/types.h b/types.h index 01e522f..09ddf0f 100644 --- a/types.h +++ b/types.h @@ -36,6 +36,15 @@ typedef void *RD_HGLYPH; typedef void *RD_HCOLOURMAP; typedef void *RD_HCURSOR; + +typedef enum _RDP_VERSION +{ + RDP_V4 = 4, + RDP_V5 = 5, + RDP_V6 = 6 +} RDP_VERSION; + + typedef struct _RD_POINT { sint16 x, y; diff --git a/xkeymap.c b/xkeymap.c index b0bb826..87baef9 100644 --- a/xkeymap.c +++ b/xkeymap.c @@ -47,7 +47,7 @@ extern int g_keyboard_subtype; extern int g_keyboard_functionkeys; extern int g_win_button_size; extern RD_BOOL g_enable_compose; -extern RD_BOOL g_use_rdp5; +extern RDP_VERSION g_rdp_version; extern RD_BOOL g_numlock_sync; static RD_BOOL keymap_loaded; @@ -467,7 +467,7 @@ send_winkey(uint32 ev_time, RD_BOOL pressed, RD_BOOL leftkey) if (pressed) { - if (g_use_rdp5) + if (g_rdp_version >= RDP_V5) { rdp_send_scancode(ev_time, RDP_KEYPRESS, winkey); } @@ -481,7 +481,7 @@ send_winkey(uint32 ev_time, RD_BOOL pressed, RD_BOOL leftkey) else { /* key released */ - if (g_use_rdp5) + if (g_rdp_version >= RDP_V5) { rdp_send_scancode(ev_time, RDP_KEYRELEASE, winkey); } @@ -496,7 +496,7 @@ send_winkey(uint32 ev_time, RD_BOOL pressed, RD_BOOL leftkey) static void reset_winkey(uint32 ev_time) { - if (g_use_rdp5) + if (g_rdp_version >= RDP_V5) { /* For some reason, it seems to suffice to release *either* the left or right winkey. */