Backport of pre-connect Hyper-V code
authorAlex Bligh <alex@alex.org.uk>
Fri, 14 Sep 2012 12:08:27 +0000 (13:08 +0100)
committerAlex Bligh <alex@alex.org.uk>
Fri, 14 Sep 2012 12:08:27 +0000 (13:08 +0100)
include/freerdp/settings.h
libfreerdp-core/connection.c
libfreerdp-core/connection.h
libfreerdp-core/nego.c
libfreerdp-core/nego.h
libfreerdp-core/settings.c
libfreerdp-utils/args.c

index 5a9acd8..44a60cf 100644 (file)
@@ -253,7 +253,8 @@ struct rdp_settings
        uint32 encryption_level; /* 28 */
        boolean authentication; /* 29 */
        uint32 negotiationFlags; /* 30 */
-       uint32 paddingB[48 - 31]; /* 31 */
+       boolean security_layer_negotiation; /* 31 */
+       uint32 paddingB[48 - 32]; /* 32 */
 
        /* Connection Settings */
        uint32 port; /* 48 */
@@ -270,7 +271,11 @@ struct rdp_settings
        boolean compression; /* 59 */
        uint32 performance_flags; /* 60 */
        rdpBlob* password_cookie; /* 61 */
-       uint32 paddingC[80 - 62]; /* 62 */
+       uint32 paddingBackport[71 - 62]; /* 62 */
+       boolean send_preconnection_pdu; /* 71 */
+       uint32 preconnection_id; /* 72 */
+       char* preconnection_blob; /* 73 */
+       uint32 paddingC[80 - 74]; /* 74 */
 
        /* User Interface Parameters */
        boolean sw_gdi; /* 80 */
index fb4fde3..1c130f9 100644 (file)
@@ -22,6 +22,7 @@
 #include "input.h"
 
 #include "connection.h"
+#include "transport.h"
 
 /**
  *                                      Connection Sequence
 
 boolean rdp_client_connect(rdpRdp* rdp)
 {
-       boolean status;
-       uint32 selectedProtocol;
        rdpSettings* settings = rdp->settings;
 
        nego_init(rdp->nego);
        nego_set_target(rdp->nego, settings->hostname, settings->port);
        nego_set_cookie(rdp->nego, settings->username);
+       nego_set_send_preconnection_pdu(rdp->nego, settings->send_preconnection_pdu);
+       nego_set_preconnection_id(rdp->nego, settings->preconnection_id);
+       nego_set_preconnection_blob(rdp->nego, settings->preconnection_blob);
+
+       nego_set_negotiation_enabled(rdp->nego, settings->security_layer_negotiation);
        nego_enable_rdp(rdp->nego, settings->rdp_security);
        nego_enable_nla(rdp->nego, settings->nla_security);
        nego_enable_tls(rdp->nego, settings->tls_security);
 
-       if (nego_connect(rdp->nego) != true)
+       if (!nego_connect(rdp->nego))
        {
-               printf("Error: protocol security negotiation failure\n");
+               printf("Error: protocol security negotiation or connection failure\n");
                return false;
        }
 
-       selectedProtocol = rdp->nego->selected_protocol;
-
-       if ((selectedProtocol & PROTOCOL_TLS) || (selectedProtocol == PROTOCOL_RDP))
+       if ((rdp->nego->selected_protocol & PROTOCOL_TLS) || (rdp->nego->selected_protocol == PROTOCOL_RDP))
        {
                if ((settings->username != NULL) && ((settings->password != NULL) || (settings->password_cookie != NULL && settings->password_cookie->length > 0)))
                        settings->autologon = true;
        }
 
-       status = false;
-       if (selectedProtocol & PROTOCOL_NLA)
-               status = transport_connect_nla(rdp->transport);
-       else if (selectedProtocol & PROTOCOL_TLS)
-               status = transport_connect_tls(rdp->transport);
-       else if (selectedProtocol == PROTOCOL_RDP) /* 0 */
-               status = transport_connect_rdp(rdp->transport);
-
-       if (status != true)
-               return false;
-
        rdp_set_blocking_mode(rdp, false);
        rdp->state = CONNECTION_STATE_NEGO;
        rdp->finalize_sc_pdus = 0;
 
-       if (mcs_send_connect_initial(rdp->mcs) != true)
+       if (!mcs_send_connect_initial(rdp->mcs))
        {
                printf("Error: unable to send MCS Connect Initial\n");
                return false;
index eb5c046..8d05bed 100644 (file)
@@ -25,7 +25,6 @@
 #include "tpdu.h"
 #include "nego.h"
 #include "mcs.h"
-#include "transport.h"
 #include "activation.h"
 
 #include <freerdp/settings.h>
index 7eb810b..64bde26 100644 (file)
@@ -27,6 +27,8 @@
 
 #include "nego.h"
 
+#include "transport.h"
+
 static const char* const NEGO_STATE_STRINGS[] =
 {
        "NEGO_STATE_INITIAL",
@@ -44,6 +46,8 @@ static const char PROTOCOL_SECURITY_STRINGS[3][4] =
        "NLA"
 };
 
+boolean nego_security_connect(rdpNego* nego);
+
 /**
  * Negotiate protocol security and connect.
  * @param nego
@@ -61,7 +65,31 @@ boolean nego_connect(rdpNego* nego)
                else if (nego->enabled_protocols[PROTOCOL_RDP] > 0)
                        nego->state = NEGO_STATE_RDP;
                else
+               {
+                       DEBUG_NEGO("No security protocol is enabled");
                        nego->state = NEGO_STATE_FAIL;
+               }
+
+               if (!nego->security_layer_negotiation_enabled)
+               {
+                       DEBUG_NEGO("Security Layer Negotiation is disabled");
+                       nego->enabled_protocols[PROTOCOL_NLA] = 0;
+                       nego->enabled_protocols[PROTOCOL_TLS] = 0;
+                       nego->enabled_protocols[PROTOCOL_RDP] = 0;
+                       if(nego->state == NEGO_STATE_NLA)
+                               nego->enabled_protocols[PROTOCOL_NLA] = 1;
+                       else if (nego->state == NEGO_STATE_TLS)
+                               nego->enabled_protocols[PROTOCOL_TLS] = 1;
+                       else if (nego->state == NEGO_STATE_RDP)
+                               nego->enabled_protocols[PROTOCOL_RDP] = 1;
+               }
+
+               if(!nego_send_preconnection_pdu(nego))
+               {
+                       DEBUG_NEGO("Failed to send preconnection information");
+                       nego->state = NEGO_STATE_FINAL;
+                       return false;
+               }
        }
 
        do
@@ -93,9 +121,35 @@ boolean nego_connect(rdpNego* nego)
                nego->transport->settings->encryption_level = ENCRYPTION_LEVEL_CLIENT_COMPATIBLE;
        }
 
+       /* finally connect security layer (if not already done) */
+       if(!nego_security_connect(nego))
+       {
+               DEBUG_NEGO("Failed to connect with %s security", PROTOCOL_SECURITY_STRINGS[nego->selected_protocol]);
+               return false;
+       }
+
        return true;
 }
 
+/* connect to selected security layer */
+boolean nego_security_connect(rdpNego* nego)
+{
+       if(!nego->tcp_connected)
+       {
+               nego->security_connected = false;
+       }
+       else if (!nego->security_connected)
+       {
+               if (nego->enabled_protocols[PROTOCOL_NLA] > 0)
+                       nego->security_connected = transport_connect_nla(nego->transport);
+               else if (nego->enabled_protocols[PROTOCOL_TLS] > 0)
+                       nego->security_connected = transport_connect_tls(nego->transport);
+               else if (nego->enabled_protocols[PROTOCOL_RDP] > 0)
+                       nego->security_connected = transport_connect_rdp(nego->transport);
+       }
+       return nego->security_connected;
+}
+
 /**
  * Connect TCP layer.
  * @param nego
@@ -104,21 +158,25 @@ boolean nego_connect(rdpNego* nego)
 
 boolean nego_tcp_connect(rdpNego* nego)
 {
-       if (nego->tcp_connected == 0)
-       {
-               if (transport_connect(nego->transport, nego->hostname, nego->port) == false)
-               {
-                       nego->tcp_connected = 0;
-                       return false;
-               }
-               else
-               {
-                       nego->tcp_connected = 1;
-                       return true;
-               }
-       }
+       if (!nego->tcp_connected)
+               nego->tcp_connected = transport_connect(nego->transport, nego->hostname, nego->port);
+       return nego->tcp_connected;
+}
 
-       return true;
+/**
+ * Connect TCP layer. For direct approach, connect security layer as well.
+ * @param nego
+ * @return
+ */
+
+boolean nego_transport_connect(rdpNego* nego)
+{
+       nego_tcp_connect(nego);
+
+       if (nego->tcp_connected && !nego->security_layer_negotiation_enabled)
+               return nego_security_connect(nego);
+
+       return nego->tcp_connected;
 }
 
 /**
@@ -127,16 +185,66 @@ boolean nego_tcp_connect(rdpNego* nego)
  * @return
  */
 
-int nego_tcp_disconnect(rdpNego* nego)
+int nego_transport_disconnect(rdpNego* nego)
 {
        if (nego->tcp_connected)
                transport_disconnect(nego->transport);
 
        nego->tcp_connected = 0;
+       nego->security_connected = 0;
        return 1;
 }
 
 /**
+ * Send preconnection information if enabled.
+ * @param nego
+ * @return
+ */
+
+boolean nego_send_preconnection_pdu(rdpNego* nego)
+{
+       STREAM* s;
+       uint32 cbSize;
+       UNICONV* uniconv;
+       uint16 cchPCB_times2 = 0;
+       char* wszPCB = NULL;
+
+       if(!nego->send_preconnection_pdu)
+               return true;
+
+       DEBUG_NEGO("Sending preconnection PDU");
+       if(!nego_tcp_connect(nego))
+               return false;
+
+       /* it's easier to always send the version 2 PDU, and it's just 2 bytes overhead */
+       cbSize = PRECONNECTION_PDU_V2_MIN_SIZE;
+       if(nego->preconnection_blob) {
+               uniconv = freerdp_uniconv_new();
+               wszPCB = freerdp_uniconv_out(uniconv, nego->preconnection_blob, &cchPCB_times2);
+               freerdp_uniconv_free(uniconv);
+               cchPCB_times2 += 2; /* zero-termination */
+               cbSize += cchPCB_times2;
+       }
+
+       s = transport_send_stream_init(nego->transport, cbSize);
+       stream_write_uint32(s, cbSize); /* cbSize */
+       stream_write_uint32(s, 0); /* Flags */
+       stream_write_uint32(s, PRECONNECTION_PDU_V2); /* Version */
+       stream_write_uint32(s, nego->preconnection_id); /* Id */
+       stream_write_uint16(s, cchPCB_times2 / 2); /* cchPCB */
+       if(wszPCB)
+       {
+               stream_write(s, wszPCB, cchPCB_times2); /* wszPCB */
+               xfree(wszPCB);
+       }
+
+       if (transport_write(nego->transport, s) < 0)
+               return false;
+
+       return true;
+}
+
+/**
  * Attempt negotiating NLA + TLS security.
  * @param nego
  */
@@ -147,7 +255,7 @@ void nego_attempt_nla(rdpNego* nego)
 
        DEBUG_NEGO("Attempting NLA security");
 
-       if (!nego_tcp_connect(nego))
+       if (!nego_transport_connect(nego))
        {
                nego->state = NEGO_STATE_FAIL;
                return;
@@ -167,7 +275,7 @@ void nego_attempt_nla(rdpNego* nego)
 
        if (nego->state != NEGO_STATE_FINAL)
        {
-               nego_tcp_disconnect(nego);
+               nego_transport_disconnect(nego);
 
                if (nego->enabled_protocols[PROTOCOL_TLS] > 0)
                        nego->state = NEGO_STATE_TLS;
@@ -189,7 +297,7 @@ void nego_attempt_tls(rdpNego* nego)
 
        DEBUG_NEGO("Attempting TLS security");
 
-       if (!nego_tcp_connect(nego))
+       if (!nego_transport_connect(nego))
        {
                nego->state = NEGO_STATE_FAIL;
                return;
@@ -209,7 +317,7 @@ void nego_attempt_tls(rdpNego* nego)
 
        if (nego->state != NEGO_STATE_FINAL)
        {
-               nego_tcp_disconnect(nego);
+               nego_transport_disconnect(nego);
 
                if (nego->enabled_protocols[PROTOCOL_RDP] > 0)
                        nego->state = NEGO_STATE_RDP;
@@ -229,7 +337,7 @@ void nego_attempt_rdp(rdpNego* nego)
 
        DEBUG_NEGO("Attempting RDP security");
 
-       if (!nego_tcp_connect(nego))
+       if (!nego_transport_connect(nego))
        {
                nego->state = NEGO_STATE_FAIL;
                return;
@@ -258,7 +366,7 @@ boolean nego_recv_response(rdpNego* nego)
        STREAM* s = transport_recv_stream_init(nego->transport, 1024);
        if (transport_read(nego->transport, s) < 0)
                return false;
-       return nego_recv(nego->transport, s, nego->transport->recv_extra);
+       return nego_recv(nego->transport, s, nego);
 }
 
 /**
@@ -662,6 +770,18 @@ void nego_set_target(rdpNego* nego, char* hostname, int port)
 }
 
 /**
+ * Enable security layer negotiation.
+ * @param nego pointer to the negotiation structure
+ * @param enable_rdp whether to enable security layer negotiation (true for enabled, false for disabled)
+ */
+
+void nego_set_negotiation_enabled(rdpNego* nego, boolean security_layer_negotiation_enabled)
+{
+       DEBUG_NEGO("Enabling security layer negotiation: %s", security_layer_negotiation_enabled ? "true" : "false");
+       nego->security_layer_negotiation_enabled = security_layer_negotiation_enabled;
+}
+
+/**
  * Enable RDP security protocol.
  * @param nego pointer to the negotiation structure
  * @param enable_rdp whether to enable normal RDP protocol (true for enabled, false for disabled)
@@ -718,3 +838,36 @@ void nego_set_cookie(rdpNego* nego, char* cookie)
 {
        nego->cookie = cookie;
 }
+
+/**
+ * Enable / disable preconnection PDU.
+ * @param nego
+ * @param send_pcpdu
+ */
+
+void nego_set_send_preconnection_pdu(rdpNego* nego, boolean send_pcpdu)
+{
+       nego->send_preconnection_pdu = send_pcpdu;
+}
+
+/**
+ * Set preconnection id.
+ * @param nego
+ * @param id
+ */
+
+void nego_set_preconnection_id(rdpNego* nego, uint32 id)
+{
+       nego->preconnection_id = id;
+}
+
+/**
+ * Set preconnection blob.
+ * @param nego
+ * @param blob
+ */
+
+void nego_set_preconnection_blob(rdpNego* nego, char* blob)
+{
+       nego->preconnection_blob = blob;
+}
index 800b3b4..cf30c94 100644 (file)
@@ -68,24 +68,40 @@ enum RDP_NEG_MSG
 
 #define EXTENDED_CLIENT_DATA_SUPPORTED 0x01
 
+#define PRECONNECTION_PDU_V1_SIZE                              16
+#define PRECONNECTION_PDU_V2_MIN_SIZE          (PRECONNECTION_PDU_V1_SIZE+2)
+
+#define PRECONNECTION_PDU_V1                                           1
+#define PRECONNECTION_PDU_V2                                           2
+
 struct rdp_nego
 {
        int port;
        uint32 flags;
        char* hostname;
        char* cookie;
-       NEGO_STATE state;
-       int tcp_connected;
        rdpBlob* routing_token;
+       boolean send_preconnection_pdu;
+       uint32 preconnection_id;
+       char* preconnection_blob;
+
+       NEGO_STATE state;
+       boolean tcp_connected;
+       boolean security_connected;
+
        uint32 selected_protocol;
        uint32 requested_protocols;
+       boolean security_layer_negotiation_enabled;
        uint8 enabled_protocols[3];
+
        rdpTransport* transport;
 };
 typedef struct rdp_nego rdpNego;
 
 boolean nego_connect(rdpNego* nego);
 
+boolean nego_send_preconnection_pdu(rdpNego* nego);
+
 void nego_attempt_nla(rdpNego* nego);
 void nego_attempt_tls(rdpNego* nego);
 void nego_attempt_rdp(rdpNego* nego);
@@ -105,11 +121,15 @@ rdpNego* nego_new(struct rdp_transport * transport);
 void nego_free(rdpNego* nego);
 void nego_init(rdpNego* nego);
 void nego_set_target(rdpNego* nego, char* hostname, int port);
+void nego_set_negotiation_enabled(rdpNego* nego, boolean security_layer_negotiation_enabled);
 void nego_enable_rdp(rdpNego* nego, boolean enable_rdp);
 void nego_enable_nla(rdpNego* nego, boolean enable_nla);
 void nego_enable_tls(rdpNego* nego, boolean enable_tls);
 void nego_set_routing_token(rdpNego* nego, rdpBlob* routing_token);
 void nego_set_cookie(rdpNego* nego, char* cookie);
+void nego_set_send_preconnection_pdu(rdpNego* nego, boolean send_pcpdu);
+void nego_set_preconnection_id(rdpNego* nego, uint32 id);
+void nego_set_preconnection_blob(rdpNego* nego, char* blob);
 
 #ifdef WITH_DEBUG_NEGO
 #define DEBUG_NEGO(fmt, ...) DEBUG_CLASS(NEGO, fmt, ## __VA_ARGS__)
index dbafa5b..95b6e46 100644 (file)
@@ -51,6 +51,7 @@ rdpSettings* settings_new(void* instance)
                settings->nla_security = true;
                settings->tls_security = true;
                settings->rdp_security = true;
+               settings->security_layer_negotiation = true;
                settings->client_build = 2600;
                settings->kbd_type = 0;
                settings->kbd_subtype = 0;
index cf10519..77d4036 100644 (file)
@@ -95,6 +95,7 @@ int freerdp_parse_args(rdpSettings* settings, int argc, char** argv,
                                "  --disable-full-window-drag: disables full window drag\n"
                                "  --disable-menu-animations: disables menu animations\n"
                                "  --disable-theming: disables theming\n"
+                               "  --no-nego: disable negotiation of security layer and enforce highest enabled security protocol\n"
                                "  --no-rdp: disable Standard RDP encryption\n"
                                "  --no-tls: disable TLS encryption\n"
                                "  --no-nla: disable network level authentication\n"
@@ -102,6 +103,8 @@ int freerdp_parse_args(rdpSettings* settings, int argc, char** argv,
                                "  --ignore-certificate: ignore verification of logon certificate\n"
                                "  --sec: force protocol security (rdp, tls or nla)\n"
                                "  --secure-checksum: use salted checksums with Standard RDP encryption\n"
+                               "  --pcid: preconnection id\n"
+                               "  --pcb: preconnection blob\n"
                                "  --version: print version information\n"
                                "\n", argv[0]);
                        return FREERDP_ARGS_PARSE_HELP; //TODO: What is the correct return
@@ -532,6 +535,10 @@ int freerdp_parse_args(rdpSettings* settings, int argc, char** argv,
                                return FREERDP_ARGS_PARSE_FAILURE;
                        }
                }
+               else if (strcmp("--no-nego", argv[index]) == 0)
+               {
+                       settings->security_layer_negotiation = false;
+               }
                else if (strcmp("--plugin", argv[index]) == 0)
                {
                        index++;
@@ -621,6 +628,28 @@ int freerdp_parse_args(rdpSettings* settings, int argc, char** argv,
                {
                        settings->secure_checksum = true;
                }
+               else if (strcmp("--pcid", argv[index]) == 0)
+               {
+                       index++;
+                       if (index == argc)
+                       {
+                               printf("missing preconnection id value\n");
+                               return -1;
+                       }
+                       settings->send_preconnection_pdu = true;
+                       settings->preconnection_id = atoi(argv[index]);
+               }
+               else if (strcmp("--pcb", argv[index]) == 0)
+               {
+                       index++;
+                       if (index == argc)
+                       {
+                               printf("missing preconnection blob value\n");
+                               return -1;
+                       }
+                       settings->send_preconnection_pdu = true;
+                       settings->preconnection_blob = xstrdup(argv[index]);
+               }
                else if (strcmp("--version", argv[index]) == 0)
                {
                        printf("This is FreeRDP version %s\n", FREERDP_VERSION_FULL);