Limited RDPv6 + CredSSP patches
David Joyner <[email protected]>
| Newsgroups | gmane.network.rdesktop.devel |
|---|---|
| Message-ID | <CDFE68B577CE9845B2FE8DFCCC8B94D7106541@edmzncmail01.products.e-dmzsecurity.com> |
I have recently finished a project that adds support for limited aspects of RDPv6 to rdesktop. Specifically I have added support for SSL and SSL + CredSSP. These are the protocols that must be used when "High Security" or "Network Level Authentication" is enabled on the server side. I've tested the attached changes against Server 2008, 2008R2 and Windows 7 and both scenarios are working well. Diffs are attached. A few notes: * These diffs were generated from SVN revision 1600. * CredSSP allows for Kerberos as well as NTLM authentication. This enhancement supports NTLMv2 only. * Where NTLMv2 is concerned, I tried to use the Heimdal NTLM library however I could not make this work. After going around for a while with Microsoft support, I came to suspect Heimdal's NTLMv2 implementation. The best alternate implementation of NTLMSSP that I could find is buried in Samba. Unfortunately, Samba does not produce externally useful shared libraries as part of its build. So, if you wish to enable CredSSP (not required for mere SSL but it is required for NLA) then you'll have to download and build Samba3 from source before you rebuild rdesktop. I used samba-3.5.5, configured with --without-winbind and then built normally. Reconfigure rdesktop, adding --with-samba=<samba-toplevel-dir> to your configure command line. With this option the rdesktop link will pull in 3-4 extra static libraries from the samba build. Thus there is no runtime dependency on Samba, only a built-time dependency. This is unfortunate and if someone could produce a better standalone NTLMSSP implementation that would obviously be a great improvement. * No other aspects of RDPv6+ were added. * This has been tested with Ubuntu 10.04.1 and Cygwin. We hope these are useful to the community and can help to jump-start RDPv6 support in the trunk. David Joyner e-DMZ Security, LLC ________________________________ The information contained in this communication is confidential, is intended only for the personal and confidential use of the recipient (s) named above. Distribution, publication, or retransmission of this message is strictly prohibited, as this message may be a vendor to client communication. If the reader of this message is not the intended recipient, you are hereby notified that any dissemination, distribution, or copying of this communication is strictly prohibited. If you have received this communication in error, please re-send this communication to the sender and delete the original message or any copy of it. Thank you ------------------------------------------------------------------------------ Increase Visibility of Your 3D Game App & Earn a Chance To Win $500! Tap into the largest installed PC base & get more eyes on your game by optimizing for Intel(R) Graphics Technology. Get started today with the Intel(R) Software Partner Program. Five $500 cash prizes are up for grabs. http://p.sf.net/sfu/intelisp-dev2dev _______________________________________________ rdesktop-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/rdesktop-devel
rdesktop-rdpv6-credssp.diff
(application/octet-stream, 51.6 KB)
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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifdef HAVE_CREDSSP
+
+#include <stdint.h>
+#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.h> /* errno */
#endif
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <openssl/err.h>
+
#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. */