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. */