diff --git a/Makefile.in b/Makefile.in --- a/Makefile.in +++ b/Makefile.in @@ -27,7 +27,7 @@ SOUNDOBJ = @SOUNDOBJ@ SCARDOBJ = @SCARDOBJ@ -RDPOBJ = tcp.o iso.o mcs.o secure.o licence.o rdp.o orders.o bitmap.o cache.o rdp5.o channels.o rdpdr.o serial.o printer.o disk.o parallel.o printercache.o mppc.o pstcache.o lspci.o seamless.o ssl.o +RDPOBJ = tcp.o iso.o credssp.o mcs.o secure.o licence.o rdp.o orders.o bitmap.o cache.o rdp5.o channels.o rdpdr.o serial.o printer.o disk.o parallel.o printercache.o mppc.o pstcache.o lspci.o seamless.o ssl.o X11OBJ = rdesktop.o xwin.o xkeymap.o ewmhints.o xclip.o cliprdr.o VNCOBJ = vnc/rdp2vnc.o vnc/vnc.o vnc/xkeymap.o vnc/x11stubs.o diff --git a/channels.c b/channels.c --- 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 @@ { 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 --- a/configure.ac +++ b/configure.ac @@ -99,12 +99,39 @@ AC_ARG_ENABLE(static-openssl, [ --enable-static-openssl link OpenSSL statically], [ -LIBS="$LIBS $ssldir/lib/libcrypto.a" +LIBS="$LIBS $ssldir/lib/libcrypto.a $ssldir/lib/libssl.a" ], [ -LIBS="$LIBS -L$ssldir/lib -lcrypto" +LIBS="$LIBS -L$ssldir/lib -lcrypto -lssl" rpath="$rpath:$ssldir/lib" ]) + +# +# Samba detection for CredSSP/NTLMSSP support +# +AC_MSG_CHECKING([for Samba3 bin directory]) +AC_ARG_WITH(samba, + [ --with-samba=DIR look for Samba3 sources at DIR], + [ + dnl Check the specified location only + if test -d "$withval/source3/bin"; then + samba3bindir="$withval/source3/bin" + AC_MSG_RESULT([$samba3bindir]) + AC_DEFINE(HAVE_CREDSSP) + for lib in libsmbclient.a libtalloc.a libtdb.a libwbclient.a; do + if test -f "$samba3bindir/$lib"; then + LIBS="$LIBS $samba3bindir/$lib" + fi + done + else + AC_MSG_RESULT([Not found]) + fi + ], + [ + AC_MSG_RESULT([Skipped]) + ] +) + # xrandr if test -n "$PKG_CONFIG"; then PKG_CHECK_MODULES(XRANDR, xrandr, [HAVE_XRANDR=1], [HAVE_XRANDR=0]) @@ -907,7 +934,7 @@ CFLAGS="$CFLAGS -D_XOPEN_SOURCE_EXTENDED" ;; *-*-irix6.5*) - LIBS="$LIBS -L$ssldir/lib32 -lcrypto" + LIBS="$LIBS -L$ssldir/lib32 -lcrypto -lssl" CFLAGS="$CFLAGS -D__SGI_IRIX__" ;; esac diff --git a/constants.h b/constants.h --- a/constants.h +++ b/constants.h @@ -33,6 +33,31 @@ ISO_PDU_ER = 0x70 /* Error */ }; +/* RDP negotiation request constants */ +enum RDP_NEG_TYPE_CODE +{ + RDP_NEG_REQ = 1, /* Negotiation request */ + RDP_NEG_RSP = 2, /* Negotiation response */ + RDP_NEG_FAILURE = 3 /* Negotiation failure */ +}; + +enum RDP_NEG_REQ_CODE +{ + PROTOCOL_RDP = 0, /* Standard RDP security */ + PROTOCOL_SSL = 1, /* TLS 1.0 */ + PROTOCOL_HYBRID = 2 /* CredSSP (over TLS) */ +}; + +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 { @@ -53,6 +78,9 @@ #define BER_TAG_INTEGER 2 #define BER_TAG_OCTET_STRING 4 #define BER_TAG_RESULT 10 +#define BER_TAG_SEQUENCE 16 +#define BER_TAG_CONSTRUCTED 0x20 +#define BER_TAG_CTXT_SPECIFIC 0x80 #define MCS_TAG_DOMAIN_PARAMS 0x30 #define MCS_GLOBAL_CHANNEL 1003 diff --git a/credssp.c b/credssp.c new file mode 100644 --- /dev/null +++ b/credssp.c @@ -0,0 +1,597 @@ +/* -*- c-basic-offset: 8 -*- + rdesktop: A Remote Desktop Protocol client. + Protocol services - CredSSP layer + Copyright (C) e-DMZ Security, LLC 2010 + + 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 . +*/ + +#ifdef HAVE_CREDSSP + +#include +#include "rdesktop.h" + +#undef CREDSSP_DEBUG + +/* Begin imports from Samba-3.5.5 */ + +typedef struct { + uint32_t v; +} NTSTATUS; + +#define NT_STATUS(x) ((NTSTATUS) { x }) +#define NT_STATUS_V(x) ((x).v) + +#define NT_STATUS_OK NT_STATUS(0x0000) +#define NT_STATUS_MORE_PROCESSING_REQUIRED NT_STATUS(0xC0000000 | 0x0016) +#define NT_STATUS_IS_OK(x) (NT_STATUS_V(x) == 0) +#define NT_STATUS_EQUAL(x,y) (NT_STATUS_V(x) == NT_STATUS_V(y)) + +typedef struct datablob { + uint8_t *data; + size_t length; +} DATA_BLOB; + +typedef struct ntlmssp_state NTLMSSP_STATE; + +#define NTLMSSP_FEATURE_SESSION_KEY 0x00000001 +#define NTLMSSP_FEATURE_SIGN 0x00000002 +#define NTLMSSP_FEATURE_SEAL 0x00000004 +#define NTLMSSP_FEATURE_CCACHE 0x00000008 + +#define NTLMSSP_SIG_SIZE 16 + +extern void init_valid_table(void); +extern int lp_do_parameter(int snum, const char *pszParmName, const char *pszParmValue); +extern int set_global_myname(const char *myname); +extern int set_global_myworkgroup(const char *myworkgroup); +extern NTSTATUS ntlmssp_client_start(NTLMSSP_STATE **ntlmssp_state); +extern void ntlmssp_want_feature(NTLMSSP_STATE *ntlmssp_state, uint32 feature); +extern NTSTATUS ntlmssp_set_username(NTLMSSP_STATE *ntlmssp_state, const char *user); +extern NTSTATUS ntlmssp_set_password(NTLMSSP_STATE *ntlmssp_state, const char *password); +extern NTSTATUS ntlmssp_set_domain(NTLMSSP_STATE *ntlmssp_state, const char *domain); +extern NTSTATUS ntlmssp_update(NTLMSSP_STATE *ntlmssp_state, const DATA_BLOB in, DATA_BLOB *out); +extern NTSTATUS ntlmssp_seal_packet(NTLMSSP_STATE *ntlmssp_state, unsigned char *data, size_t length, unsigned char *whole_pdu, size_t pdu_length, DATA_BLOB *sig); +extern NTSTATUS ntlmssp_unseal_packet(NTLMSSP_STATE *ntlmssp_state, unsigned char *data, size_t length, unsigned char *whole_pdu, size_t pdu_length, DATA_BLOB *sig); +extern void ntlmssp_end(NTLMSSP_STATE **ntlmssp_state); +extern void data_blob_free(DATA_BLOB *d); + +/* End Samba imports */ + + +static void +ntlmssp_init(void) +{ + static RD_BOOL init = False; + + if (!init) + { + init_valid_table(); + lp_do_parameter(-1, "client NTLMv2 auth", "true"); + set_global_myworkgroup(""); + set_global_myname(""); + init = True; + } +} + +static int +ber_length(int value_len) +{ + /* One byte of tag + one/three bytes of length + value */ + return (value_len >= 0x80) ? (4 + value_len) : (2 + value_len); +} + +static void +ber_out_header(STREAM s, int tagval, int *encoded_len) +{ + out_uint8(s, tagval); + + if (*encoded_len >= 0x82) + { + out_uint8(s, 0x82); + *encoded_len -= 4; + out_uint16_be(s, *encoded_len); + } + else + { + *encoded_len -= 2; + out_uint8(s, *encoded_len); + } +} + +static RD_BOOL +ber_in_header(STREAM s, int *tagval, int *decoded_len) +{ + in_uint8(s, *tagval); + in_uint8(s, *decoded_len); + + if (*decoded_len < 0x80) + return True; + else if (*decoded_len == 0x81) + { + in_uint8(s, *decoded_len); + return True; + } + else if (*decoded_len == 0x82) + { + in_uint16_be(s, *decoded_len); + return True; + } + + return False; +} + +/* Encode a CredSSP TSCredentials structure */ +static void +encode_tscredentials(char *domain, char *username, char *password, STREAM s, size_t overhead) +{ + int cred_type_len; + int domain_len, username_len, password_len; + int ts_pw_creds_len, ts_creds_len; + int total_len; + int i; + + /* + * Account for ASN.1 BER encoding of message fields. Also note that the domain, username + * and password fields are sent using UTF-16LE encoding. + */ + + /* TSCredentials ::= SEQUENCE { ... } */ + { + /* credType [0] INTEGER */ + cred_type_len = ber_length(3); + + /* credentials [1] OCTET STRING */ + { + /* TSPasswordCreds ::= SEQUENCE { ... } */ + + /* domainName [0] OCTET STRING */ + domain_len = ber_length(domain ? (2 * strlen(domain)) : 0); + domain_len = ber_length(domain_len); + + /* userName [1] OCTET STRING */ + username_len = ber_length(username ? (2 * strlen(username)) : 0); + username_len = ber_length(username_len); + + /* password [2] OCTET STRING */ + password_len = ber_length(password ? (2 * strlen(password)) : 0); + password_len = ber_length(password_len); + + ts_pw_creds_len = ber_length(domain_len + username_len + password_len); + } + + ts_creds_len = ber_length(ts_pw_creds_len); + ts_creds_len = ber_length(ts_creds_len); + } + + total_len = ber_length(cred_type_len + ts_creds_len); + + /* + * Populate stream and encode message + */ + s->data = s->p = xmalloc(total_len + overhead); + s->end = s->data + total_len + overhead; + s->size = total_len + overhead; + + out_uint8s(s, overhead); + ber_out_header(s, BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED, &total_len); + + /* credType [0] */ + ber_out_header(s, BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 0, &cred_type_len); + out_uint8(s, BER_TAG_INTEGER); + out_uint8(s, 1); + out_uint8(s, 1); /* TSPasswordCreds */ + + /* credentials [1] */ + ber_out_header(s, BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 1, &ts_creds_len); + ber_out_header(s, BER_TAG_OCTET_STRING, &ts_creds_len); + + ber_out_header(s, BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED, &ts_pw_creds_len); + + ber_out_header(s, BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 0, &domain_len); + ber_out_header(s, BER_TAG_OCTET_STRING, &domain_len); + for (domain_len /= 2, i = 0; i < domain_len; i++) + out_uint16_le(s, domain[i]); + + ber_out_header(s, BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 1, &username_len); + ber_out_header(s, BER_TAG_OCTET_STRING, &username_len); + for (username_len /= 2, i = 0; i < username_len; i++) + out_uint16_le(s, username[i]); + + ber_out_header(s, BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 2, &password_len); + ber_out_header(s, BER_TAG_OCTET_STRING, &password_len); + for (password_len /= 2, i = 0; i < password_len; i++) + out_uint16_le(s, password[i]); +} + +/* Send a CredSSP TSRequest */ +static void +credssp_send_tsrequest(STREAM nego_token, STREAM auth_info, STREAM pub_key_auth) +{ + int nego_token_len = nego_token ? (nego_token->end - nego_token->data) : 0; + int auth_info_len = auth_info ? (auth_info->end - auth_info->data) : 0; + int pub_key_auth_len = pub_key_auth ? (pub_key_auth->end - pub_key_auth->data) : 0; + int ver_len, total_len; + STREAM s; + + /* + * Account for ASN.1 BER encoding of message fields + */ + + /* version [0] INTEGER */ + ver_len = ber_length(3); + + /* negoTokens [1] NegoData OPTIONAL */ + if (nego_token_len) + { + /* NegoData ::= SEQUENCE { SEQUENCE { [0] OCTET STRING } } */ + nego_token_len = ber_length(nego_token_len); + nego_token_len = ber_length(nego_token_len); + nego_token_len = ber_length(nego_token_len); + nego_token_len = ber_length(nego_token_len); + nego_token_len = ber_length(nego_token_len); + } + + /* authInfo [2] OCTET STRING OPTIONAL */ + if (auth_info_len) + { + auth_info_len = ber_length(auth_info_len); + auth_info_len = ber_length(auth_info_len); + } + + /* pubKeyAuth [3] OCTET STRING OPTIONAL */ + if (pub_key_auth_len) + { + pub_key_auth_len = ber_length(pub_key_auth_len); + pub_key_auth_len = ber_length(pub_key_auth_len); + } + + /* Total message length */ + total_len = ber_length(ver_len + nego_token_len + auth_info_len + pub_key_auth_len); + + /* + * Create stream and encode message + */ + s = tcp_init(total_len); + ber_out_header(s, BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED, &total_len); + + /* version [0] */ + ber_out_header(s, BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 0, &ver_len); + out_uint8(s, BER_TAG_INTEGER); + out_uint8(s, 1); + out_uint8(s, 2); /* version = 2 */ + + /* negoTokens [1] */ + if (nego_token_len) + { + ber_out_header(s, BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 1, &nego_token_len); + ber_out_header(s, BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED, &nego_token_len); + ber_out_header(s, BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED, &nego_token_len); + ber_out_header(s, BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 0, &nego_token_len); + ber_out_header(s, BER_TAG_OCTET_STRING, &nego_token_len); + out_uint8p(s, nego_token->data, nego_token_len); +#ifdef CREDSSP_DEBUG + printf("out (nego_token)\n"); + hexdump(nego_token->data, nego_token_len); +#endif + } + + /* authInfo [2] */ + if (auth_info_len) + { + ber_out_header(s, BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 2, &auth_info_len); + ber_out_header(s, BER_TAG_OCTET_STRING, &auth_info_len); + out_uint8p(s, auth_info->data, auth_info_len); +#ifdef CREDSSP_DEBUG + printf("out (auth_info)\n"); + hexdump(auth_info->data, auth_info_len); +#endif + } + + /* pubKeyAuth [3] */ + if (pub_key_auth_len) + { + ber_out_header(s, BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 3, &pub_key_auth_len); + ber_out_header(s, BER_TAG_OCTET_STRING, &pub_key_auth_len); + out_uint8p(s, pub_key_auth->data, pub_key_auth_len); +#ifdef CREDSSP_DEBUG + printf("out (pub_key_auth)\n"); + hexdump(pub_key_auth->data, pub_key_auth_len); +#endif + } + + s_mark_end(s); + +#ifdef CREDSSP_DEBUG + printf("out (raw)\n"); + hexdump(s->data, s->end - s->data); +#endif + tcp_send(s); +} + +/* Receive a CredSSP TSRequest */ +static RD_BOOL +credssp_recv_tsrequest(STREAM nego_token, STREAM pub_key_auth) +{ + STREAM s; + int length; + int tagval; + + if (nego_token) + { + nego_token->data = nego_token->p = NULL; + nego_token->size = 0; + } + + if (pub_key_auth) + { + pub_key_auth->data = pub_key_auth->p = NULL; + pub_key_auth->size = 0; + } + + /* Peek at ASN.1 BER encoded message to determine total length */ + s = tcp_recv(NULL, 4); + if (s == NULL) + return False; + if (s->p[0] != (BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED)) + return False; + if (s->p[1] < 0x80) + length = s->p[1] - 2; + else if (s->p[1] == 0x81) + length = s->p[2] - 1; + else if (s->p[1] == 0x82) + length = (s->p[2] << 8) | s->p[3]; + else + return False; + + /* Receive remainder of the message */ + s = tcp_recv(s, length); +#ifdef CREDSSP_DEBUG + printf("in (raw)\n"); + hexdump(s->p, s->end - s->data); +#endif + + /* Strip/parse BER headers to get to nego_tokens */ + if (!ber_in_header(s, &tagval, &length) || tagval != (BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED)) + return False; + + if (!ber_in_header(s, &tagval, &length) || tagval != (BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 0)) + return False; + + in_uint8s(s, length); /* completely skip version [0] */ + + if (nego_token) + { + if (!ber_in_header(s, &tagval, &length) || tagval != (BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 1)) + return False; + + if (!ber_in_header(s, &tagval, &length) || tagval != (BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED)) + return False; + + if (!ber_in_header(s, &tagval, &length) || tagval != (BER_TAG_SEQUENCE | BER_TAG_CONSTRUCTED)) + return False; + + if (!ber_in_header(s, &tagval, &length) || tagval != (BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 0)) + return False; + + if (!ber_in_header(s, &tagval, &length) || tagval != BER_TAG_OCTET_STRING) + return False; + + nego_token->data = nego_token->p = s->p; + nego_token->end = nego_token->data + length; + nego_token->size = length; +#ifdef CREDSSP_DEBUG + printf("in (nego_token)\n"); + hexdump(nego_token->p, nego_token->end - nego_token->p); +#endif + } + + if (pub_key_auth) + { + if (!ber_in_header(s, &tagval, &length) || tagval != (BER_TAG_CTXT_SPECIFIC | BER_TAG_CONSTRUCTED | 3)) + return False; + + if (!ber_in_header(s, &tagval, &length) || tagval != BER_TAG_OCTET_STRING) + return False; + + pub_key_auth->data = pub_key_auth->p = s->p; + pub_key_auth->end = pub_key_auth->data + length; + pub_key_auth->size = length; +#ifdef CREDSSP_DEBUG + printf("in (pub_key_auth)\n"); + hexdump(pub_key_auth->p, pub_key_auth->end - pub_key_auth->p); +#endif + } + + return True; +} + +/* Establish a CredSSP connection */ +RD_BOOL +credssp_connect(char *domain, char *username, char *password) +{ + NTLMSSP_STATE *ntlmssp_state = NULL; + NTSTATUS nt_status; + DATA_BLOB blob_in = { 0 }; + DATA_BLOB blob_out, blob_mic; + struct stream pub_key = { 0 }; + struct stream auth_info = { 0 }; + struct stream nego_token, pub_key_auth; + size_t pub_key_len; + RD_BOOL connected = False; + + /* Establish TLS connection */ + if (!tcp_tls_connect()) + goto out; + + if (!tcp_tls_getserverpublickey(&pub_key)) + goto out; + + pub_key_len = pub_key.end - pub_key.data; + + /* NTLMSSP initialization */ + ntlmssp_init(); + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_client_start(&ntlmssp_state))) + { + error("credssp_connect: ntlmssp_client_start failed\n"); + goto out; + } + + ntlmssp_want_feature(ntlmssp_state, NTLMSSP_FEATURE_SESSION_KEY); + ntlmssp_want_feature(ntlmssp_state, NTLMSSP_FEATURE_SIGN); + ntlmssp_want_feature(ntlmssp_state, NTLMSSP_FEATURE_SEAL); + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_domain(ntlmssp_state, domain))) + { + error("credssp_connect: ntlmssp_set_domain failed\n"); + goto out; + } + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_username(ntlmssp_state, username))) + { + error("credssp_connect: ntlmssp_set_username failed\n"); + goto out; + } + + if (!NT_STATUS_IS_OK(nt_status = ntlmssp_set_password(ntlmssp_state, password))) + { + error("credssp_connect: ntlmssp_set_password failed\n"); + goto out; + } + + /* Complete mutual NTLMSSP authentication */ + do + { + nt_status = ntlmssp_update(ntlmssp_state, blob_in, &blob_out); + if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED) && !NT_STATUS_IS_OK(nt_status)) + { + error("credssp_connect: ntlmssp_update failed\n"); + goto out; + } + + if (blob_out.length) + { + nego_token.size = blob_out.length; + nego_token.data = nego_token.p = blob_out.data; + nego_token.end = nego_token.data + blob_out.length; + + if (NT_STATUS_IS_OK(nt_status)) + { + /* Send encrypted form of server's public key in final NTLMSSP message */ + pub_key_auth.size = NTLMSSP_SIG_SIZE + pub_key_len; + pub_key_auth.data = pub_key_auth.p = xmalloc(pub_key_auth.size); + pub_key_auth.end = pub_key_auth.data + pub_key_auth.size; + + memcpy(pub_key_auth.data + NTLMSSP_SIG_SIZE, pub_key.data, pub_key_len); + + if (!NT_STATUS_IS_OK(ntlmssp_seal_packet(ntlmssp_state, + pub_key_auth.data + NTLMSSP_SIG_SIZE, pub_key_len, + pub_key_auth.data + NTLMSSP_SIG_SIZE, pub_key_len, + &blob_mic))) + { + error("credssp_connect: ntlmssp_seal_packet failed (pub_key_auth)\n"); + goto out; + } + + memcpy(pub_key_auth.data, blob_mic.data, NTLMSSP_SIG_SIZE); + data_blob_free(&blob_mic); + + credssp_send_tsrequest(&nego_token, NULL, &pub_key_auth); + data_blob_free(&blob_out); + xfree(pub_key_auth.data); + } + else + { + /* Send regular NTLMSSP message */ + credssp_send_tsrequest(&nego_token, NULL, NULL); + data_blob_free(&blob_out); + } + } + + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) + { + if (!credssp_recv_tsrequest(&nego_token, NULL)) + goto out; + + blob_in.length = nego_token.end - nego_token.p; + blob_in.data = nego_token.p; + } + } while (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)); + + /* Receive server's public key echo */ + if (!credssp_recv_tsrequest(NULL, &pub_key_auth)) + { + error("credssp_connect: failed to receive server response\n"); + goto out; + } + + /* Decrypt the server's public key, subtract one from the first byte and compare */ + in_uint8p(&pub_key_auth, blob_mic.data, NTLMSSP_SIG_SIZE); + blob_mic.length = NTLMSSP_SIG_SIZE; + + if (!NT_STATUS_IS_OK(ntlmssp_unseal_packet(ntlmssp_state, + pub_key_auth.p, pub_key_auth.end - pub_key_auth.p, + pub_key_auth.p, pub_key_auth.end - pub_key_auth.p, + &blob_mic))) + { + error("credssp_connect: ntlmssp_unseal_packet failed\n"); + goto out; + } + + if ((pub_key_auth.end - pub_key_auth.p) != pub_key_len) + { + error("credssp_connect: cannot guarantee integrity of server connection, man in the middle? (public key length mismatch)\n"); + goto out; + } + + pub_key_auth.p[0] -= 1; + if (memcmp(pub_key.data, pub_key_auth.p, pub_key_len) != 0) + { + error("credssp_connect: cannot guarantee integrity of server connection, man in the middle? (public key data mismatch)\n"); + goto out; + } + + /* Send encrypted form of user's credentials */ + encode_tscredentials(domain, username, password, &auth_info, NTLMSSP_SIG_SIZE); + + if (!NT_STATUS_IS_OK(ntlmssp_seal_packet(ntlmssp_state, + auth_info.data + NTLMSSP_SIG_SIZE, auth_info.size - NTLMSSP_SIG_SIZE, + auth_info.data + NTLMSSP_SIG_SIZE, auth_info.size - NTLMSSP_SIG_SIZE, + &blob_mic))) + { + error("credssp_connect: ntlmssp_seal_packet failed (auth_info)\n"); + goto out; + } + + memcpy(auth_info.data, blob_mic.data, NTLMSSP_SIG_SIZE); + data_blob_free(&blob_mic); + + credssp_send_tsrequest(NULL, &auth_info, NULL); + connected = True; + +out: + if (ntlmssp_state) + ntlmssp_end(&ntlmssp_state); + + if (pub_key.data) + xfree(pub_key.data); + + if (auth_info.data) + xfree(auth_info.data); + + return connected; +} + +#endif /* HAVE_CREDSSP */ diff --git a/iso.c b/iso.c --- a/iso.c +++ b/iso.c @@ -19,6 +19,9 @@ #include "rdesktop.h" +extern RD_BOOL g_encryption; +extern RDP_VERSION g_rdp_version; + /* Send a self-contained ISO PDU */ static void iso_send_msg(uint8 code) @@ -47,6 +50,9 @@ STREAM s; int length = 30 + strlen(username); + if (g_rdp_version >= RDP_V6) + length += 8; + s = tcp_init(length); out_uint8(s, 3); /* version */ @@ -65,6 +71,18 @@ out_uint8(s, 0x0d); /* Unknown */ out_uint8(s, 0x0a); /* Unknown */ + if (g_rdp_version >= RDP_V6) + { + out_uint8(s, RDP_NEG_REQ); + out_uint8(s, 0); + out_uint16(s, 8); +#ifdef HAVE_CREDSSP + out_uint32(s, PROTOCOL_SSL | PROTOCOL_HYBRID); +#else + out_uint32(s, PROTOCOL_SSL); +#endif + } + s_mark_end(s); tcp_send(s); } @@ -173,9 +191,13 @@ /* Establish a connection up to the ISO layer */ RD_BOOL -iso_connect(char *server, char *username, RD_BOOL reconnect) +iso_connect(char *server, char *domain, char *username, char *password, RD_BOOL reconnect, uint32 *selected_protocol) { - uint8 code = 0; + STREAM s; + uint8 code; + +retry: + code = 0; if (!tcp_connect(server)) return False; @@ -189,7 +211,8 @@ 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) @@ -199,6 +222,106 @@ return False; } + if (g_rdp_version >= RDP_V6 && s_check_rem(s, 8)) + { + uint8 type = 0; + uint32 data = 0; + const char *reason = NULL; + + in_uint8(s, type); + in_uint8s(s, 3); + in_uint32(s, data); + + switch (type) + { + case RDP_NEG_RSP: + if (data == PROTOCOL_SSL) + { + /* Establish TLS connection */ + if (!tcp_tls_connect()) + { + tcp_disconnect(); + return False; + } + + /* Turn off native RDP encryption -- TLS is providing this for us at the transport layer */ + g_encryption = False; + } +#ifdef HAVE_CREDSSP + else if (data == PROTOCOL_HYBRID) + { + /* Delegate credentials to server via CredSSP (automatically establishes TLS connection) */ + if (!credssp_connect(domain, username, password)) + { + tcp_disconnect(); + warning("failed to connect with RDPv%u, retrying with RDPv5\n", g_rdp_version); + g_rdp_version = RDP_V5; + goto retry; + } + + /* Turn off native RDP encryption -- TLS is providing this for us at the transport layer */ + g_encryption = False; + } +#endif + else if (data != PROTOCOL_RDP) + { + tcp_disconnect(); + error("unexpected protocol, got 0x%x\n", data); + return False; + } + + *selected_protocol = data; + break; + + case RDP_NEG_FAILURE: + switch (data) + { + case SSL_REQUIRED_BY_SERVER: + reason = "SSL 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 certificate not on server"; + break; + + case INCONSISTENT_FLAGS: + reason = "inconsistent flags"; + break; + + case HYBRID_REQUIRED_BY_SERVER: + reason = "hybrid authentication (CredSSP) required by server"; + break; + + case SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER: + reason = "SSL with user authentication required by server"; + break; + + default: + reason = "unknown reason"; + break; + } + + tcp_disconnect(); + warning("got RDP_NEG_FAILURE, %s (failure code = 0x%x)\n", reason, data); + warning("failed to connect with RDPv%u, retrying with RDPv5\n", g_rdp_version); + g_rdp_version = RDP_V5; + goto retry; + + default: + tcp_disconnect(); + error("expected RDP_NEG_RSP, got type = 0x%x\n", type); + warning("failed to connect with RDPv%u, retrying with RDPv5\n", g_rdp_version); + g_rdp_version = RDP_V5; + goto retry; + } + } + else + *selected_protocol = PROTOCOL_RDP; + return True; } diff --git a/licence.c b/licence.c --- a/licence.c +++ b/licence.c @@ -22,11 +22,13 @@ extern char *g_username; extern char g_hostname[16]; +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 @@ -315,7 +317,10 @@ break; case LICENCE_TAG_REISSUE: + break; + case LICENCE_TAG_RESULT: + g_licence_error_result = True; break; default: diff --git a/mcs.c b/mcs.c --- a/mcs.c +++ b/mcs.c @@ -372,13 +372,16 @@ } RD_BOOL -mcs_connect(char *server, STREAM mcs_data, char *username, RD_BOOL reconnect) +mcs_connect_start(char *server, char *domain, char *username, char *password, RD_BOOL reconnect, uint32 *selected_protocol) +{ + return iso_connect(server, domain, username, password, reconnect, selected_protocol); +} + +RD_BOOL +mcs_connect(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 --- 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 @@ 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 --- a/proto.h +++ b/proto.h @@ -19,6 +19,12 @@ #ifndef RDESKTOP_PROTO_H #define RDESKTOP_PROTO_H +#ifdef HAVE_CREDSSP +/* Horrible hacks to avoid link-time collisions with Samba3 functions */ +#define generate_random _rdesktop_generate_random +#define sec_init _rdesktop_sec_init +#endif + /* *INDENT-OFF* */ #ifdef __cplusplus extern "C" { @@ -57,6 +63,8 @@ void cliprdr_send_data(uint8 * data, uint32 length); void cliprdr_set_mode(const char *optarg); RD_BOOL cliprdr_init(void); +/* credssp.c */ +RD_BOOL credssp_connect(char *domain, char *username, char *password); /* disk.c */ int disk_enum_devices(uint32 * id, char *optarg); RD_NTSTATUS disk_query_information(RD_NTHANDLE handle, uint32 info_class, STREAM out); @@ -74,7 +82,7 @@ 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 *domain, char *username, char *password, RD_BOOL reconnect, uint32 *selected_protocol); void iso_disconnect(void); void iso_reset_state(void); /* licence.c */ @@ -84,7 +92,8 @@ 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 *domain, char *username, char *password, RD_BOOL reconnect, uint32 *selected_protocol); +RD_BOOL mcs_connect(STREAM mcs_data); void mcs_disconnect(void); void mcs_reset_state(void); /* orders.c */ @@ -187,7 +196,7 @@ void sec_send(STREAM s, uint32 flags); void sec_process_mcs_data(STREAM s); STREAM sec_recv(uint8 * rdpver); -RD_BOOL sec_connect(char *server, char *username, RD_BOOL reconnect); +RD_BOOL sec_connect(char *server, char *domain, char *username, char *password, RD_BOOL reconnect); void sec_disconnect(void); void sec_reset_state(void); /* serial.c */ @@ -200,8 +209,12 @@ void tcp_send(STREAM s); STREAM tcp_recv(STREAM s, uint32 length); RD_BOOL tcp_connect(char *server); +RD_BOOL tcp_tls_connect(void); +RD_BOOL tcp_tls_getserverpublickey(STREAM s); void tcp_disconnect(void); char *tcp_get_address(void); +RD_BOOL tcp_get_sockname(uint32 *addr); +RD_BOOL tcp_get_peername(uint32 *addr); void tcp_reset_state(void); /* xclip.c */ void ui_clip_format_announce(uint8 * data, uint32 length); diff --git a/rdesktop.c b/rdesktop.c --- a/rdesktop.c +++ b/rdesktop.c @@ -81,7 +81,7 @@ 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_V6; RD_BOOL g_rdpclip = True; RD_BOOL g_console_session = False; RD_BOOL g_numlock_sync = False; @@ -215,7 +215,8 @@ #endif fprintf(stderr, " -0: attach to console\n"); fprintf(stderr, " -4: use RDP version 4\n"); - fprintf(stderr, " -5: use RDP version 5 (default)\n"); + fprintf(stderr, " -5: use RDP version 5\n"); + fprintf(stderr, " -6: use RDP version 6 (default)\n"); } static int @@ -488,7 +489,7 @@ #endif while ((c = getopt(argc, argv, - VNCOPT "Au:L:d:s:c:p:n:k:g:fbBeEmzCDKS:T:NX:a:x:Pr:045h?")) != -1) + VNCOPT "Au:L:d:s:c:p:n:k:g:fbBeEmzCDKS:T:NX:a:x:Pr:0456h?")) != -1) { switch (c) { @@ -511,8 +512,18 @@ break; case 'u': - g_username = (char *) xmalloc(strlen(optarg) + 1); - STRNCPY(g_username, optarg, strlen(optarg) + 1); + if (!domain[0] && (p = strchr(optarg, '\\')) != NULL) + { + STRNCPY(domain, optarg, p - optarg + 1); + p++; + g_username = (char *) xmalloc(strlen(p) + 1); + STRNCPY(g_username, p, strlen(p) + 1); + } + else + { + g_username = (char *) xmalloc(strlen(optarg) + 1); + STRNCPY(g_username, optarg, strlen(optarg) + 1); + } username_option = 1; break; @@ -817,11 +828,15 @@ 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 '6': + g_rdp_version = RDP_V6; break; case 'h': @@ -869,7 +884,7 @@ 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 --- a/rdp.c +++ b/rdp.c @@ -43,7 +43,7 @@ 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; @@ -340,7 +340,7 @@ time_t tzone; uint8 security_verifier[16]; - if (!g_use_rdp5 || 1 == g_server_rdp_version) + if (g_rdp_version < RDP_V5 || 1 == g_server_rdp_version) { DEBUG_RDP5(("Sending RDP4-style Logon packet\n")); @@ -643,7 +643,7 @@ 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. @@ -895,7 +895,7 @@ 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; @@ -924,7 +924,7 @@ 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); @@ -959,7 +959,7 @@ in_uint16_le(s, pad2octetsB); if (!pad2octetsB) - g_use_rdp5 = False; + g_rdp_version = RDP_V4; } /* Process a bitmap capability set */ @@ -1059,7 +1059,7 @@ 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); @@ -1632,10 +1632,21 @@ rdp_connect(char *server, uint32 flags, char *domain, char *password, char *command, char *directory, RD_BOOL reconnect) { - if (!sec_connect(server, g_username, reconnect)) + RD_BOOL deactivated; + uint32 ext_disc_reason; + + if (!sec_connect(server, domain, g_username, password, reconnect)) return False; rdp_send_logon_info(flags, domain, g_username, password, command, directory); + + /* Run the RDP loop briefly until we've processed the first 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 --- a/secure.c +++ b/secure.c @@ -29,7 +29,8 @@ 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[]; @@ -290,7 +291,7 @@ 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; @@ -311,7 +312,7 @@ #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) @@ -364,10 +365,10 @@ /* 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) @@ -395,8 +396,8 @@ /* 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.0 - RDP7.0 */ out_uint16_le(s, 8); out_uint16_le(s, g_width); out_uint16_le(s, g_height); @@ -419,11 +420,12 @@ out_uint16_le(s, 1); out_uint32(s, 0); - out_uint8(s, g_server_depth); + out_uint8(s, g_server_depth); 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); @@ -446,7 +448,7 @@ { DEBUG_RDP5(("Requesting channel %s\n", g_channels[i].name)); out_uint8a(s, g_channels[i].name, 8); - out_uint32_be(s, g_channels[i].flags); + out_uint32_le(s, g_channels[i].flags); } } @@ -695,7 +697,7 @@ 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; } } @@ -770,56 +772,74 @@ return s; } } - if (g_encryption || !g_licence_issued) + if (g_encryption || (!g_licence_issued && !g_licence_error_result)) { - in_uint32_le(s, sec_flags); + in_uint32_le(s, sec_flags); - if (sec_flags & SEC_ENCRYPT) - { - in_uint8s(s, 8); /* signature */ - sec_decrypt(s->p, s->end - s->p); - } + if (g_encryption) + { + if (sec_flags & SEC_ENCRYPT) + { + 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 & SEC_LICENCE_NEG) + { + licence_process(s); + continue; + } - if (sec_flags & 0x0400) /* SEC_REDIRECT_ENCRYPT */ - { - uint8 swapbyte; + if (sec_flags & 0x0400) /* SEC_REDIRECT_ENCRYPT */ + { + uint8 swapbyte; - in_uint8s(s, 8); /* signature */ - sec_decrypt(s->p, s->end - s->p); + 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; + /* 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[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; - } + 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); + /* warning! this debug statement will show passwords in the clear! */ + hexdump(s->p, s->end - s->p); #endif - } + } + } + else + { + /* We're not running encryption and haven't completed licencing yet... + * This may be a licencing packet with a security header but there is no sure + * fire way to tell. If the flags indicate licence negotiation then assume + * that this is indeed a licencing packet and proceed. Otherwise assume that + * there is no security header and rewind the stream. + */ + if ((sec_flags & 0xffff) == SEC_LICENCE_NEG) + { + licence_process(s); + continue; + } + s->p -= 4; + } } if (channel != MCS_GLOBAL_CHANNEL) @@ -838,16 +858,21 @@ /* Establish a secure connection */ RD_BOOL -sec_connect(char *server, char *username, RD_BOOL reconnect) +sec_connect(char *server, char *domain, char *username, char *password, RD_BOOL reconnect) { + uint32 selected_protocol; struct stream mcs_data; + /* Start the MCS connection process to obtain the selected security protocol */ + if (!mcs_connect_start(server, domain, username, password, reconnect, &selected_protocol)) + 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_protocol); - if (!mcs_connect(server, &mcs_data, username, reconnect)) + if (!mcs_connect(&mcs_data)) return False; /* sec_process_mcs_data(&mcs_data); */ diff --git a/tcp.c b/tcp.c --- a/tcp.c +++ b/tcp.c @@ -28,6 +28,10 @@ #include /* errno */ #endif +#include +#include +#include + #include "rdesktop.h" #ifdef _WIN32 @@ -54,6 +58,8 @@ static int g_sock; static struct stream g_in; static struct stream g_out[STREAM_COUNT]; +static SSL_CTX *g_ssl_ctx = NULL; +static SSL *g_ssl = NULL; int g_tcp_port_rdp = TCP_PORT_RDP; extern RD_BOOL g_user_quit; @@ -116,20 +122,46 @@ #endif while (total < length) { - 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 - { - error("send: %s\n", TCP_STRERROR); - return; - } - } + if (g_ssl) + { + sent = SSL_write(g_ssl, s->data + total, length - total); + if (sent <= 0) + { + int 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 + { + 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 + { + error("send: %s\n", TCP_STRERROR); + return; + } + } + } + +#if 0 + printf("tcp_send: %d bytes\n", sent); + hexdump(s->data + total, sent); +#endif total += sent; } #ifdef WITH_SCARD @@ -172,34 +204,79 @@ 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 (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; - } + if (g_ssl) + { + rcvd = SSL_read(g_ssl, s->end, length); + switch (SSL_get_error(g_ssl, rcvd)) + { + case SSL_ERROR_NONE: +#ifdef SSL_DEBUG + printf("SSL_read: %d bytes\n", rcvd); + hexdump(s->end, rcvd); +#endif + s->end += rcvd; + length -= rcvd; + break; - s->end += rcvd; - length -= rcvd; + case SSL_ERROR_WANT_READ: +#ifdef SSL_DEBUG + printf("SSL_ERROR_WANT_READ\n"); +#endif + rcvd = 0; + break; + + case SSL_ERROR_WANT_WRITE: +#ifdef SSL_DEBUG + printf("SSL_ERROR_WANT_WRITE\n"); +#endif + rcvd = 0; + break; + + case SSL_ERROR_SSL: + ERR_print_errors_fp(stdout); + return NULL; + + default: + error("SSL_read: %d (%s)\n", SSL_get_error(g_ssl, rcvd), TCP_STRERROR); + return NULL; + } + } + else + { + 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; + length -= rcvd; + } + +#if 0 + printf("tcp_recv: %d bytes\n", rcvd); + hexdump(s->end - rcvd, rcvd); +#endif } return s; @@ -313,10 +390,128 @@ return True; } +/* Support for SSL/TLS 1.0 */ +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 a TLS v1.0 client\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); + g_ssl = NULL; + } + + if (g_ssl_ctx) + { + SSL_CTX_free(g_ssl_ctx); + g_ssl_ctx = NULL; + } + + return False; +} + +RD_BOOL +tcp_tls_getserverpublickey(STREAM s) +{ + X509 *cert = NULL; + EVP_PKEY *pkey = NULL; + + s->data = s->p = NULL; + s->size = 0; + + if (!g_ssl) + goto out; + + cert = SSL_get_peer_certificate(g_ssl); + if (!cert) + { + error("tcp_tls_getserverpublickey: SSL_get_peer_certificate() failed\n"); + goto out; + } + + pkey = X509_get_pubkey(cert); + if (!pkey) + { + error("tcp_tls_getserverpublickey: X509_get_pubkey() failed\n"); + goto out; + } + + s->size = i2d_PublicKey(pkey, NULL); + if (s->size < 1) + { + error("tcp_tls_getserverpublickey: 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); +} + /* Disconnect on the TCP layer */ 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); } @@ -336,6 +531,34 @@ return ipaddr; } +RD_BOOL +tcp_get_sockname(uint32 *addr) +{ + struct sockaddr_in sockaddr; + socklen_t len = sizeof(sockaddr); + if (getsockname(g_sock, (struct sockaddr *) &sockaddr, &len) == 0) + { + *addr = sockaddr.sin_addr.s_addr; + return True; + } + else + return False; +} + +RD_BOOL +tcp_get_peername(uint32 *addr) +{ + struct sockaddr_in sockaddr; + socklen_t len = sizeof(sockaddr); + if (getpeername(g_sock, (struct sockaddr *) &sockaddr, &len) == 0) + { + *addr = sockaddr.sin_addr.s_addr; + return True; + } + else + return False; +} + /* reset the state of the tcp layer */ /* Support for Session Directory */ void diff --git a/types.h b/types.h --- a/types.h +++ b/types.h @@ -31,6 +31,14 @@ typedef unsigned int uint32; typedef signed int sint32; +typedef enum _RDP_VERSION +{ + RDP_V4 = 4, + RDP_V5 = 5, + RDP_V6 = 6 +} +RDP_VERSION; + typedef void *RD_HBITMAP; typedef void *RD_HGLYPH; typedef void *RD_HCOLOURMAP; diff --git a/xkeymap.c b/xkeymap.c --- a/xkeymap.c +++ b/xkeymap.c @@ -47,7 +47,7 @@ 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 @@ if (pressed) { - if (g_use_rdp5) + if (g_rdp_version >= RDP_V5) { rdp_send_scancode(ev_time, RDP_KEYPRESS, winkey); } @@ -481,7 +481,7 @@ 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 @@ 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. */