diff options
Diffstat (limited to 'libfreerdp/core/gcc.c')
-rw-r--r-- | libfreerdp/core/gcc.c | 2404 |
1 files changed, 2404 insertions, 0 deletions
diff --git a/libfreerdp/core/gcc.c b/libfreerdp/core/gcc.c new file mode 100644 index 0000000..d99ee86 --- /dev/null +++ b/libfreerdp/core/gcc.c @@ -0,0 +1,2404 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * T.124 Generic Conference Control (GCC) + * + * Copyright 2011 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2014 Norbert Federa <norbert.federa@thincast.com> + * Copyright 2014 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * Copyright 2023 Armin Novak <anovak@thincast.com> + * Copyright 2023 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include "settings.h" + +#include <winpr/crt.h> +#include <winpr/crypto.h> +#include <winpr/assert.h> + +#include <freerdp/log.h> +#include <freerdp/utils/string.h> +#include <freerdp/crypto/certificate.h> + +#include "utils.h" +#include "gcc.h" +#include "nego.h" + +#include "../crypto/certificate.h" + +#define TAG FREERDP_TAG("core.gcc") + +typedef enum +{ + HIGH_COLOR_4BPP = 0x04, + HIGH_COLOR_8BPP = 0x08, + HIGH_COLOR_15BPP = 0x0F, + HIGH_COLOR_16BPP = 0x10, + HIGH_COLOR_24BPP = 0x18, +} HIGH_COLOR_DEPTH; + +static const char* HighColorToString(HIGH_COLOR_DEPTH color) +{ + switch (color) + { + case HIGH_COLOR_4BPP: + return "HIGH_COLOR_4BPP"; + case HIGH_COLOR_8BPP: + return "HIGH_COLOR_8BPP"; + case HIGH_COLOR_15BPP: + return "HIGH_COLOR_15BPP"; + case HIGH_COLOR_16BPP: + return "HIGH_COLOR_16BPP"; + case HIGH_COLOR_24BPP: + return "HIGH_COLOR_24BPP"; + default: + return "HIGH_COLOR_UNKNOWN"; + } +} + +static HIGH_COLOR_DEPTH ColorDepthToHighColor(UINT32 bpp) +{ + switch (bpp) + { + case 4: + return HIGH_COLOR_4BPP; + case 8: + return HIGH_COLOR_8BPP; + case 15: + return HIGH_COLOR_15BPP; + case 16: + return HIGH_COLOR_16BPP; + default: + return HIGH_COLOR_24BPP; + } +} + +static char* gcc_block_type_string(UINT16 type, char* buffer, size_t size); +static BOOL gcc_read_client_cluster_data(wStream* s, rdpMcs* mcs); +static BOOL gcc_read_client_core_data(wStream* s, rdpMcs* mcs); +static BOOL gcc_read_client_data_blocks(wStream* s, rdpMcs* mcs, UINT16 length); +static BOOL gcc_read_server_data_blocks(wStream* s, rdpMcs* mcs, UINT16 length); +static BOOL gcc_read_user_data_header(wStream* s, UINT16* type, UINT16* length); +static BOOL gcc_write_user_data_header(wStream* s, UINT16 type, UINT16 length); + +static BOOL gcc_write_client_core_data(wStream* s, const rdpMcs* mcs); +static BOOL gcc_read_server_core_data(wStream* s, rdpMcs* mcs); +static BOOL gcc_write_server_core_data(wStream* s, rdpMcs* mcs); +static BOOL gcc_read_client_security_data(wStream* s, rdpMcs* mcs); +static BOOL gcc_write_client_security_data(wStream* s, const rdpMcs* mcs); +static BOOL gcc_read_server_security_data(wStream* s, rdpMcs* mcs); +static BOOL gcc_write_server_security_data(wStream* s, rdpMcs* mcs); +static BOOL gcc_read_client_network_data(wStream* s, rdpMcs* mcs); +static BOOL gcc_write_client_network_data(wStream* s, const rdpMcs* mcs); +static BOOL gcc_read_server_network_data(wStream* s, rdpMcs* mcs); +static BOOL gcc_write_server_network_data(wStream* s, const rdpMcs* mcs); +static BOOL gcc_write_client_cluster_data(wStream* s, const rdpMcs* mcs); +static BOOL gcc_read_client_monitor_data(wStream* s, rdpMcs* mcs); +static BOOL gcc_write_client_monitor_data(wStream* s, const rdpMcs* mcs); +static BOOL gcc_read_client_monitor_extended_data(wStream* s, rdpMcs* mcs); +static BOOL gcc_write_client_monitor_extended_data(wStream* s, const rdpMcs* mcs); +static BOOL gcc_read_client_message_channel_data(wStream* s, rdpMcs* mcs); +static BOOL gcc_write_client_message_channel_data(wStream* s, const rdpMcs* mcs); +static BOOL gcc_read_server_message_channel_data(wStream* s, rdpMcs* mcs); +static BOOL gcc_write_server_message_channel_data(wStream* s, const rdpMcs* mcs); +static BOOL gcc_read_client_multitransport_channel_data(wStream* s, rdpMcs* mcs); +static BOOL gcc_write_client_multitransport_channel_data(wStream* s, const rdpMcs* mcs); +static BOOL gcc_read_server_multitransport_channel_data(wStream* s, rdpMcs* mcs); +static BOOL gcc_write_server_multitransport_channel_data(wStream* s, const rdpMcs* mcs); + +static rdpSettings* mcs_get_settings(rdpMcs* mcs) +{ + WINPR_ASSERT(mcs); + + rdpContext* context = transport_get_context(mcs->transport); + WINPR_ASSERT(context); + + return context->settings; +} + +static const rdpSettings* mcs_get_const_settings(const rdpMcs* mcs) +{ + WINPR_ASSERT(mcs); + + const rdpContext* context = transport_get_context(mcs->transport); + WINPR_ASSERT(context); + + return context->settings; +} + +static char* rdp_early_server_caps_string(UINT32 flags, char* buffer, size_t size) +{ + char msg[32] = { 0 }; + const UINT32 mask = RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V1 | RNS_UD_SC_DYNAMIC_DST_SUPPORTED | + RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V2 | RNS_UD_SC_SKIP_CHANNELJOIN_SUPPORTED; + const UINT32 unknown = flags & (~mask); + + if (flags & RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V1) + winpr_str_append("RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V1", buffer, size, "|"); + if (flags & RNS_UD_SC_DYNAMIC_DST_SUPPORTED) + winpr_str_append("RNS_UD_SC_DYNAMIC_DST_SUPPORTED", buffer, size, "|"); + if (flags & RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V2) + winpr_str_append("RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V2", buffer, size, "|"); + if (flags & RNS_UD_SC_SKIP_CHANNELJOIN_SUPPORTED) + winpr_str_append("RNS_UD_SC_SKIP_CHANNELJOIN_SUPPORTED", buffer, size, "|"); + + if (unknown != 0) + { + _snprintf(msg, sizeof(msg), "RNS_UD_SC_UNKNOWN[0x%08" PRIx32 "]", unknown); + winpr_str_append(msg, buffer, size, "|"); + } + _snprintf(msg, sizeof(msg), "[0x%08" PRIx32 "]", flags); + winpr_str_append(msg, buffer, size, "|"); + return buffer; +} + +static const char* rdp_early_client_caps_string(UINT32 flags, char* buffer, size_t size) +{ + char msg[32] = { 0 }; + const UINT32 mask = RNS_UD_CS_SUPPORT_ERRINFO_PDU | RNS_UD_CS_WANT_32BPP_SESSION | + RNS_UD_CS_SUPPORT_STATUSINFO_PDU | RNS_UD_CS_STRONG_ASYMMETRIC_KEYS | + RNS_UD_CS_RELATIVE_MOUSE_INPUT | RNS_UD_CS_VALID_CONNECTION_TYPE | + RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU | + RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT | + RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL | RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE | + RNS_UD_CS_SUPPORT_HEARTBEAT_PDU | RNS_UD_CS_SUPPORT_SKIP_CHANNELJOIN; + const UINT32 unknown = flags & (~mask); + + if (flags & RNS_UD_CS_SUPPORT_ERRINFO_PDU) + winpr_str_append("RNS_UD_CS_SUPPORT_ERRINFO_PDU", buffer, size, "|"); + if (flags & RNS_UD_CS_WANT_32BPP_SESSION) + winpr_str_append("RNS_UD_CS_WANT_32BPP_SESSION", buffer, size, "|"); + if (flags & RNS_UD_CS_SUPPORT_STATUSINFO_PDU) + winpr_str_append("RNS_UD_CS_SUPPORT_STATUSINFO_PDU", buffer, size, "|"); + if (flags & RNS_UD_CS_STRONG_ASYMMETRIC_KEYS) + winpr_str_append("RNS_UD_CS_STRONG_ASYMMETRIC_KEYS", buffer, size, "|"); + if (flags & RNS_UD_CS_RELATIVE_MOUSE_INPUT) + winpr_str_append("RNS_UD_CS_RELATIVE_MOUSE_INPUT", buffer, size, "|"); + if (flags & RNS_UD_CS_VALID_CONNECTION_TYPE) + winpr_str_append("RNS_UD_CS_VALID_CONNECTION_TYPE", buffer, size, "|"); + if (flags & RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU) + winpr_str_append("RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU", buffer, size, "|"); + if (flags & RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT) + winpr_str_append("RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT", buffer, size, "|"); + if (flags & RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL) + winpr_str_append("RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL", buffer, size, "|"); + if (flags & RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE) + winpr_str_append("RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE", buffer, size, "|"); + if (flags & RNS_UD_CS_SUPPORT_HEARTBEAT_PDU) + winpr_str_append("RNS_UD_CS_SUPPORT_HEARTBEAT_PDU", buffer, size, "|"); + if (flags & RNS_UD_CS_SUPPORT_SKIP_CHANNELJOIN) + winpr_str_append("RNS_UD_CS_SUPPORT_SKIP_CHANNELJOIN", buffer, size, "|"); + + if (unknown != 0) + { + _snprintf(msg, sizeof(msg), "RNS_UD_CS_UNKNOWN[0x%08" PRIx32 "]", unknown); + winpr_str_append(msg, buffer, size, "|"); + } + _snprintf(msg, sizeof(msg), "[0x%08" PRIx32 "]", flags); + winpr_str_append(msg, buffer, size, "|"); + return buffer; +} + +static DWORD rdp_version_common(DWORD serverVersion, DWORD clientVersion) +{ + DWORD version = MIN(serverVersion, clientVersion); + + switch (version) + { + case RDP_VERSION_4: + case RDP_VERSION_5_PLUS: + case RDP_VERSION_10_0: + case RDP_VERSION_10_1: + case RDP_VERSION_10_2: + case RDP_VERSION_10_3: + case RDP_VERSION_10_4: + case RDP_VERSION_10_5: + case RDP_VERSION_10_6: + case RDP_VERSION_10_7: + case RDP_VERSION_10_8: + case RDP_VERSION_10_9: + case RDP_VERSION_10_10: + case RDP_VERSION_10_11: + case RDP_VERSION_10_12: + return version; + + default: + WLog_ERR(TAG, "Invalid client [%" PRId32 "] and server [%" PRId32 "] versions", + serverVersion, clientVersion); + return version; + } +} + +/** + * T.124 GCC is defined in: + * + * http://www.itu.int/rec/T-REC-T.124-199802-S/en + * ITU-T T.124 (02/98): Generic Conference Control + */ + +/** + * ConnectData ::= SEQUENCE + * { + * t124Identifier Key, + * connectPDU OCTET_STRING + * } + * + * Key ::= CHOICE + * { + * object OBJECT_IDENTIFIER, + * h221NonStandard H221NonStandardIdentifier + * } + * + * ConnectGCCPDU ::= CHOICE + * { + * conferenceCreateRequest ConferenceCreateRequest, + * conferenceCreateResponse ConferenceCreateResponse, + * conferenceQueryRequest ConferenceQueryRequest, + * conferenceQueryResponse ConferenceQueryResponse, + * conferenceJoinRequest ConferenceJoinRequest, + * conferenceJoinResponse ConferenceJoinResponse, + * conferenceInviteRequest ConferenceInviteRequest, + * conferenceInviteResponse ConferenceInviteResponse, + * ... + * } + * + * ConferenceCreateRequest ::= SEQUENCE + * { + * conferenceName ConferenceName, + * convenerPassword Password OPTIONAL, + * password Password OPTIONAL, + * lockedConference BOOLEAN, + * listedConference BOOLEAN, + * conductibleConference BOOLEAN, + * terminationMethod TerminationMethod, + * conductorPrivileges SET OF Privilege OPTIONAL, + * conductedPrivileges SET OF Privilege OPTIONAL, + * nonConductedPrivileges SET OF Privilege OPTIONAL, + * conferenceDescription TextString OPTIONAL, + * callerIdentifier TextString OPTIONAL, + * userData UserData OPTIONAL, + * ..., + * conferencePriority ConferencePriority OPTIONAL, + * conferenceMode ConferenceMode OPTIONAL + * } + * + * ConferenceCreateResponse ::= SEQUENCE + * { + * nodeID UserID, + * tag INTEGER, + * result ENUMERATED + * { + * success (0), + * userRejected (1), + * resourcesNotAvailable (2), + * rejectedForSymmetryBreaking (3), + * lockedConferenceNotSupported (4) + * }, + * userData UserData OPTIONAL, + * ... + * } + * + * ConferenceName ::= SEQUENCE + * { + * numeric SimpleNumericString + * text SimpleTextString OPTIONAL, + * ... + * } + * + * SimpleNumericString ::= NumericString (SIZE (1..255)) (FROM ("0123456789")) + * + * UserData ::= SET OF SEQUENCE + * { + * key Key, + * value OCTET_STRING OPTIONAL + * } + * + * H221NonStandardIdentifier ::= OCTET STRING (SIZE (4..255)) + * + * UserID ::= DynamicChannelID + * + * ChannelID ::= INTEGER (1..65535) + * StaticChannelID ::= INTEGER (1..1000) + * DynamicChannelID ::= INTEGER (1001..65535) + * + */ + +/* + * OID = 0.0.20.124.0.1 + * { itu-t(0) recommendation(0) t(20) t124(124) version(0) 1 } + * v.1 of ITU-T Recommendation T.124 (Feb 1998): "Generic Conference Control" + */ +static const BYTE t124_02_98_oid[6] = { 0, 0, 20, 124, 0, 1 }; + +static const BYTE h221_cs_key[4] = "Duca"; +static const BYTE h221_sc_key[4] = "McDn"; + +/** + * Read a GCC Conference Create Request. + * msdn{cc240836} + * + * @param s stream + * @param mcs The MCS instance + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_read_conference_create_request(wStream* s, rdpMcs* mcs) +{ + UINT16 length = 0; + BYTE choice = 0; + BYTE number = 0; + BYTE selection = 0; + + WINPR_ASSERT(s); + WINPR_ASSERT(mcs); + /* ConnectData */ + if (!per_read_choice(s, &choice)) + return FALSE; + + if (!per_read_object_identifier(s, t124_02_98_oid)) + return FALSE; + + /* ConnectData::connectPDU (OCTET_STRING) */ + if (!per_read_length(s, &length)) + return FALSE; + + /* ConnectGCCPDU */ + if (!per_read_choice(s, &choice)) + return FALSE; + + if (!per_read_selection(s, &selection)) + return FALSE; + + /* ConferenceCreateRequest::conferenceName */ + if (!per_read_numeric_string(s, 1)) /* ConferenceName::numeric */ + return FALSE; + + if (!per_read_padding(s, 1)) /* padding */ + return FALSE; + + /* UserData (SET OF SEQUENCE) */ + if (!per_read_number_of_sets(s, &number) || number != 1) /* one set of UserData */ + return FALSE; + + if (!per_read_choice(s, &choice) || + choice != 0xC0) /* UserData::value present + select h221NonStandard (1) */ + return FALSE; + + /* h221NonStandard */ + if (!per_read_octet_string(s, h221_cs_key, 4, + 4)) /* h221NonStandard, client-to-server H.221 key, "Duca" */ + return FALSE; + + /* userData::value (OCTET_STRING) */ + if (!per_read_length(s, &length)) + return FALSE; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, length)) + return FALSE; + + if (!gcc_read_client_data_blocks(s, mcs, length)) + return FALSE; + + return TRUE; +} + +/** + * Write a GCC Conference Create Request. + * msdn{cc240836} + * + * @param s stream + * @param userData client data blocks + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_write_conference_create_request(wStream* s, wStream* userData) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(userData); + /* ConnectData */ + if (!per_write_choice(s, 0)) /* From Key select object (0) of type OBJECT_IDENTIFIER */ + return FALSE; + if (!per_write_object_identifier(s, t124_02_98_oid)) /* ITU-T T.124 (02/98) OBJECT_IDENTIFIER */ + return FALSE; + /* ConnectData::connectPDU (OCTET_STRING) */ + if (!per_write_length(s, Stream_GetPosition(userData) + 14)) /* connectPDU length */ + return FALSE; + /* ConnectGCCPDU */ + if (!per_write_choice(s, 0)) /* From ConnectGCCPDU select conferenceCreateRequest (0) of type + ConferenceCreateRequest */ + return FALSE; + if (!per_write_selection(s, 0x08)) /* select optional userData from ConferenceCreateRequest */ + return FALSE; + /* ConferenceCreateRequest::conferenceName */ + if (!per_write_numeric_string(s, (BYTE*)"1", 1, 1)) /* ConferenceName::numeric */ + return FALSE; + if (!per_write_padding(s, 1)) /* padding */ + return FALSE; + /* UserData (SET OF SEQUENCE) */ + if (!per_write_number_of_sets(s, 1)) /* one set of UserData */ + return FALSE; + if (!per_write_choice(s, 0xC0)) /* UserData::value present + select h221NonStandard (1) */ + return FALSE; + /* h221NonStandard */ + if (!per_write_octet_string(s, h221_cs_key, 4, + 4)) /* h221NonStandard, client-to-server H.221 key, "Duca" */ + return FALSE; + /* userData::value (OCTET_STRING) */ + return per_write_octet_string(s, Stream_Buffer(userData), Stream_GetPosition(userData), + 0); /* array of client data blocks */ +} + +BOOL gcc_read_conference_create_response(wStream* s, rdpMcs* mcs) +{ + UINT16 length = 0; + UINT32 tag = 0; + UINT16 nodeID = 0; + BYTE result = 0; + BYTE choice = 0; + BYTE number = 0; + WINPR_ASSERT(s); + WINPR_ASSERT(mcs); + /* ConnectData */ + if (!per_read_choice(s, &choice) || !per_read_object_identifier(s, t124_02_98_oid)) + return FALSE; + + /* ConnectData::connectPDU (OCTET_STRING) */ + if (!per_read_length(s, &length)) + return FALSE; + + /* ConnectGCCPDU */ + if (!per_read_choice(s, &choice)) + return FALSE; + + /* ConferenceCreateResponse::nodeID (UserID) */ + if (!per_read_integer16(s, &nodeID, 1001)) + return FALSE; + + /* ConferenceCreateResponse::tag (INTEGER) */ + if (!per_read_integer(s, &tag)) + return FALSE; + + /* ConferenceCreateResponse::result (ENUMERATED) */ + if (!per_read_enumerated(s, &result, MCS_Result_enum_length)) + return FALSE; + + /* number of UserData sets */ + if (!per_read_number_of_sets(s, &number)) + return FALSE; + + /* UserData::value present + select h221NonStandard (1) */ + if (!per_read_choice(s, &choice)) + return FALSE; + + /* h221NonStandard */ + if (!per_read_octet_string(s, h221_sc_key, 4, + 4)) /* h221NonStandard, server-to-client H.221 key, "McDn" */ + return FALSE; + + /* userData (OCTET_STRING) */ + if (!per_read_length(s, &length)) + return FALSE; + + if (!gcc_read_server_data_blocks(s, mcs, length)) + { + WLog_ERR(TAG, "gcc_read_conference_create_response: gcc_read_server_data_blocks failed"); + return FALSE; + } + + return TRUE; +} + +BOOL gcc_write_conference_create_response(wStream* s, wStream* userData) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(userData); + /* ConnectData */ + if (!per_write_choice(s, 0)) + return FALSE; + if (!per_write_object_identifier(s, t124_02_98_oid)) + return FALSE; + /* ConnectData::connectPDU (OCTET_STRING) */ + /* This length MUST be ignored by the client according to [MS-RDPBCGR] */ + if (!per_write_length(s, 0x2A)) + return FALSE; + /* ConnectGCCPDU */ + if (!per_write_choice(s, 0x14)) + return FALSE; + /* ConferenceCreateResponse::nodeID (UserID) */ + if (!per_write_integer16(s, 0x79F3, 1001)) + return FALSE; + /* ConferenceCreateResponse::tag (INTEGER) */ + if (!per_write_integer(s, 1)) + return FALSE; + /* ConferenceCreateResponse::result (ENUMERATED) */ + if (!per_write_enumerated(s, 0, MCS_Result_enum_length)) + return FALSE; + /* number of UserData sets */ + if (!per_write_number_of_sets(s, 1)) + return FALSE; + /* UserData::value present + select h221NonStandard (1) */ + if (!per_write_choice(s, 0xC0)) + return FALSE; + /* h221NonStandard */ + if (!per_write_octet_string(s, h221_sc_key, 4, + 4)) /* h221NonStandard, server-to-client H.221 key, "McDn" */ + return FALSE; + /* userData (OCTET_STRING) */ + return per_write_octet_string(s, Stream_Buffer(userData), Stream_GetPosition(userData), + 0); /* array of server data blocks */ +} + +static BOOL gcc_read_client_unused1_data(wStream* s) +{ + return Stream_SafeSeek(s, 2); +} + +BOOL gcc_read_client_data_blocks(wStream* s, rdpMcs* mcs, UINT16 length) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(mcs); + while (length > 0) + { + wStream sbuffer = { 0 }; + UINT16 type = 0; + UINT16 blockLength = 0; + + if (!gcc_read_user_data_header(s, &type, &blockLength)) + return FALSE; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(blockLength - 4))) + return FALSE; + + wStream* sub = Stream_StaticConstInit(&sbuffer, Stream_Pointer(s), blockLength - 4); + WINPR_ASSERT(sub); + + Stream_Seek(s, blockLength - 4); + + switch (type) + { + case CS_CORE: + if (!gcc_read_client_core_data(sub, mcs)) + return FALSE; + + break; + + case CS_SECURITY: + if (!gcc_read_client_security_data(sub, mcs)) + return FALSE; + + break; + + case CS_NET: + if (!gcc_read_client_network_data(sub, mcs)) + return FALSE; + + break; + + case CS_CLUSTER: + if (!gcc_read_client_cluster_data(sub, mcs)) + return FALSE; + + break; + + case CS_MONITOR: + if (!gcc_read_client_monitor_data(sub, mcs)) + return FALSE; + + break; + + case CS_MCS_MSGCHANNEL: + if (!gcc_read_client_message_channel_data(sub, mcs)) + return FALSE; + + break; + + case CS_MONITOR_EX: + if (!gcc_read_client_monitor_extended_data(sub, mcs)) + return FALSE; + + break; + + case CS_UNUSED1: + if (!gcc_read_client_unused1_data(sub)) + return FALSE; + + break; + + case 0xC009: + case CS_MULTITRANSPORT: + if (!gcc_read_client_multitransport_channel_data(sub, mcs)) + return FALSE; + + break; + + default: + WLog_ERR(TAG, "Unknown GCC client data block: 0x%04" PRIX16 "", type); + winpr_HexDump(TAG, WLOG_TRACE, Stream_Pointer(sub), Stream_GetRemainingLength(sub)); + break; + } + + const size_t rem = Stream_GetRemainingLength(sub); + if (rem > 0) + { + char buffer[128] = { 0 }; + const size_t total = Stream_Length(sub); + WLog_ERR(TAG, + "Error parsing GCC client data block %s: Actual Offset: %" PRIuz + " Expected Offset: %" PRIuz, + gcc_block_type_string(type, buffer, sizeof(buffer)), total - rem, total); + } + + if (blockLength > length) + { + char buffer[128] = { 0 }; + WLog_ERR(TAG, + "Error parsing GCC client data block %s: got blockLength 0x%04" PRIx16 + ", but only 0x%04" PRIx16 "remaining", + gcc_block_type_string(type, buffer, sizeof(buffer)), blockLength, length); + length = 0; + } + else + length -= blockLength; + } + + return TRUE; +} + +BOOL gcc_write_client_data_blocks(wStream* s, const rdpMcs* mcs) +{ + const rdpSettings* settings = mcs_get_const_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + if (!gcc_write_client_core_data(s, mcs) || !gcc_write_client_cluster_data(s, mcs) || + !gcc_write_client_security_data(s, mcs) || !gcc_write_client_network_data(s, mcs)) + return FALSE; + + /* extended client data supported */ + + if (settings->NegotiationFlags & EXTENDED_CLIENT_DATA_SUPPORTED) + { + if (settings->UseMultimon && !settings->SpanMonitors) + { + if (!gcc_write_client_monitor_data(s, mcs) || + !gcc_write_client_monitor_extended_data(s, mcs)) + return FALSE; + } + + if (!gcc_write_client_message_channel_data(s, mcs) || + !gcc_write_client_multitransport_channel_data(s, mcs)) + return FALSE; + } + else + { + if (settings->UseMultimon && !settings->SpanMonitors) + { + WLog_ERR(TAG, "WARNING: true multi monitor support was not advertised by server!"); + + if (settings->ForceMultimon) + { + WLog_ERR(TAG, "Sending multi monitor information anyway (may break connectivity!)"); + if (!gcc_write_client_monitor_data(s, mcs) || + !gcc_write_client_monitor_extended_data(s, mcs)) + return FALSE; + } + else + { + WLog_ERR(TAG, "Use /multimon:force to force sending multi monitor information"); + } + } + } + return TRUE; +} + +char* gcc_block_type_string(UINT16 type, char* buffer, size_t size) +{ + switch (type) + { + case CS_CORE: + _snprintf(buffer, size, "CS_CORE [0x%04" PRIx16 "]", type); + break; + case CS_SECURITY: + _snprintf(buffer, size, "CS_SECURITY [0x%04" PRIx16 "]", type); + break; + case CS_NET: + _snprintf(buffer, size, "CS_NET [0x%04" PRIx16 "]", type); + break; + case CS_CLUSTER: + _snprintf(buffer, size, "CS_CLUSTER [0x%04" PRIx16 "]", type); + break; + case CS_MONITOR: + _snprintf(buffer, size, "CS_MONITOR [0x%04" PRIx16 "]", type); + break; + case CS_MCS_MSGCHANNEL: + _snprintf(buffer, size, "CS_MONITOR [0x%04" PRIx16 "]", type); + break; + case CS_MONITOR_EX: + _snprintf(buffer, size, "CS_MONITOR_EX [0x%04" PRIx16 "]", type); + break; + case CS_UNUSED1: + _snprintf(buffer, size, "CS_UNUSED1 [0x%04" PRIx16 "]", type); + break; + case CS_MULTITRANSPORT: + _snprintf(buffer, size, "CS_MONITOR_EX [0x%04" PRIx16 "]", type); + break; + case SC_CORE: + _snprintf(buffer, size, "SC_CORE [0x%04" PRIx16 "]", type); + break; + case SC_SECURITY: + _snprintf(buffer, size, "SC_SECURITY [0x%04" PRIx16 "]", type); + break; + case SC_NET: + _snprintf(buffer, size, "SC_NET [0x%04" PRIx16 "]", type); + break; + case SC_MCS_MSGCHANNEL: + _snprintf(buffer, size, "SC_MCS_MSGCHANNEL [0x%04" PRIx16 "]", type); + break; + case SC_MULTITRANSPORT: + _snprintf(buffer, size, "SC_MULTITRANSPORT [0x%04" PRIx16 "]", type); + break; + default: + _snprintf(buffer, size, "UNKNOWN [0x%04" PRIx16 "]", type); + break; + } + return buffer; +} + +BOOL gcc_read_server_data_blocks(wStream* s, rdpMcs* mcs, UINT16 length) +{ + UINT16 type = 0; + UINT16 offset = 0; + UINT16 blockLength = 0; + BYTE* holdp = NULL; + + WINPR_ASSERT(s); + WINPR_ASSERT(mcs); + + while (offset < length) + { + char buffer[64] = { 0 }; + size_t rest = 0; + wStream subbuffer; + wStream* sub = NULL; + + if (!gcc_read_user_data_header(s, &type, &blockLength)) + { + WLog_ERR(TAG, "gcc_read_server_data_blocks: gcc_read_user_data_header failed"); + return FALSE; + } + holdp = Stream_Pointer(s); + sub = Stream_StaticInit(&subbuffer, holdp, blockLength - 4); + if (!Stream_SafeSeek(s, blockLength - 4)) + { + WLog_ERR(TAG, "gcc_read_server_data_blocks: stream too short"); + return FALSE; + } + offset += blockLength; + + switch (type) + { + case SC_CORE: + if (!gcc_read_server_core_data(sub, mcs)) + { + WLog_ERR(TAG, "gcc_read_server_data_blocks: gcc_read_server_core_data failed"); + return FALSE; + } + + break; + + case SC_SECURITY: + if (!gcc_read_server_security_data(sub, mcs)) + return FALSE; + break; + + case SC_NET: + if (!gcc_read_server_network_data(sub, mcs)) + { + WLog_ERR(TAG, + "gcc_read_server_data_blocks: gcc_read_server_network_data failed"); + return FALSE; + } + + break; + + case SC_MCS_MSGCHANNEL: + if (!gcc_read_server_message_channel_data(sub, mcs)) + { + WLog_ERR( + TAG, + "gcc_read_server_data_blocks: gcc_read_server_message_channel_data failed"); + return FALSE; + } + + break; + + case SC_MULTITRANSPORT: + if (!gcc_read_server_multitransport_channel_data(sub, mcs)) + { + WLog_ERR(TAG, "gcc_read_server_data_blocks: " + "gcc_read_server_multitransport_channel_data failed"); + return FALSE; + } + + break; + + default: + WLog_ERR(TAG, "gcc_read_server_data_blocks: ignoring type=%s", + gcc_block_type_string(type, buffer, sizeof(buffer))); + winpr_HexDump(TAG, WLOG_TRACE, Stream_Pointer(sub), Stream_GetRemainingLength(sub)); + break; + } + + rest = Stream_GetRemainingLength(sub); + if (rest > 0) + { + WLog_WARN(TAG, "gcc_read_server_data_blocks: ignoring %" PRIuz " bytes with type=%s", + rest, gcc_block_type_string(type, buffer, sizeof(buffer))); + } + } + + return TRUE; +} + +BOOL gcc_write_server_data_blocks(wStream* s, rdpMcs* mcs) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(mcs); + + if (!gcc_write_server_core_data(s, mcs) || /* serverCoreData */ + !gcc_write_server_network_data(s, mcs) || /* serverNetworkData */ + !gcc_write_server_security_data(s, mcs) || /* serverSecurityData */ + !gcc_write_server_message_channel_data(s, mcs)) /* serverMessageChannelData */ + return FALSE; + + const rdpSettings* settings = mcs_get_const_settings(mcs); + WINPR_ASSERT(settings); + + if (settings->SupportMultitransport && (settings->MultitransportFlags != 0)) + /* serverMultitransportChannelData */ + return gcc_write_server_multitransport_channel_data(s, mcs); + + return TRUE; +} + +BOOL gcc_read_user_data_header(wStream* s, UINT16* type, UINT16* length) +{ + WINPR_ASSERT(s); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return FALSE; + + Stream_Read_UINT16(s, *type); /* type */ + Stream_Read_UINT16(s, *length); /* length */ + + if ((*length < 4) || (!Stream_CheckAndLogRequiredLength(TAG, s, (size_t)(*length - 4)))) + return FALSE; + + return TRUE; +} + +/** + * Write a user data header (TS_UD_HEADER). + * msdn{cc240509} + * + * @param s stream + * @param type data block type + * @param length data block length + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_write_user_data_header(wStream* s, UINT16 type, UINT16 length) +{ + + WINPR_ASSERT(s); + if (!Stream_EnsureRemainingCapacity(s, 4 + length)) + return FALSE; + Stream_Write_UINT16(s, type); /* type */ + Stream_Write_UINT16(s, length); /* length */ + return TRUE; +} + +static UINT32 filterAndLogEarlyServerCapabilityFlags(UINT32 flags) +{ + const UINT32 mask = + (RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V1 | RNS_UD_SC_DYNAMIC_DST_SUPPORTED | + RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V2 | RNS_UD_SC_SKIP_CHANNELJOIN_SUPPORTED); + const UINT32 filtered = flags & mask; + const UINT32 unknown = flags & (~mask); + if (unknown != 0) + { + char buffer[256] = { 0 }; + WLog_WARN(TAG, + "TS_UD_SC_CORE::EarlyCapabilityFlags [0x%08" PRIx32 " & 0x%08" PRIx32 + " --> 0x%08" PRIx32 "] filtering %s, feature not implemented", + flags, ~mask, unknown, + rdp_early_server_caps_string(unknown, buffer, sizeof(buffer))); + } + return filtered; +} + +static UINT32 earlyServerCapsFromSettings(const rdpSettings* settings) +{ + UINT32 EarlyCapabilityFlags = 0; + + if (settings->SupportEdgeActionV1) + EarlyCapabilityFlags |= RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V1; + if (settings->SupportDynamicTimeZone) + EarlyCapabilityFlags |= RNS_UD_SC_DYNAMIC_DST_SUPPORTED; + if (settings->SupportEdgeActionV2) + EarlyCapabilityFlags |= RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V2; + if (settings->SupportSkipChannelJoin) + EarlyCapabilityFlags |= RNS_UD_SC_SKIP_CHANNELJOIN_SUPPORTED; + + return filterAndLogEarlyServerCapabilityFlags(EarlyCapabilityFlags); +} + +static UINT16 filterAndLogEarlyClientCapabilityFlags(UINT32 flags) +{ + const UINT32 mask = + (RNS_UD_CS_SUPPORT_ERRINFO_PDU | RNS_UD_CS_WANT_32BPP_SESSION | + RNS_UD_CS_SUPPORT_STATUSINFO_PDU | RNS_UD_CS_STRONG_ASYMMETRIC_KEYS | + RNS_UD_CS_RELATIVE_MOUSE_INPUT | RNS_UD_CS_VALID_CONNECTION_TYPE | + RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU | RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT | + RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL | RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE | + RNS_UD_CS_SUPPORT_HEARTBEAT_PDU | RNS_UD_CS_SUPPORT_SKIP_CHANNELJOIN); + const UINT32 filtered = flags & mask; + const UINT32 unknown = flags & ~mask; + if (unknown != 0) + { + char buffer[256] = { 0 }; + WLog_WARN(TAG, + "(TS_UD_CS_CORE)::EarlyCapabilityFlags [0x%08" PRIx32 " & 0x%08" PRIx32 + " --> 0x%08" PRIx32 "] filtering %s, feature not implemented", + flags, ~mask, unknown, + rdp_early_client_caps_string(unknown, buffer, sizeof(buffer))); + } + return filtered; +} + +static UINT16 earlyClientCapsFromSettings(const rdpSettings* settings) +{ + UINT32 earlyCapabilityFlags = 0; + + WINPR_ASSERT(settings); + if (settings->SupportErrorInfoPdu) + earlyCapabilityFlags |= RNS_UD_CS_SUPPORT_ERRINFO_PDU; + + if (freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth) == 32) + earlyCapabilityFlags |= RNS_UD_CS_WANT_32BPP_SESSION; + + if (settings->SupportStatusInfoPdu) + earlyCapabilityFlags |= RNS_UD_CS_SUPPORT_STATUSINFO_PDU; + + if (settings->ConnectionType) + earlyCapabilityFlags |= RNS_UD_CS_VALID_CONNECTION_TYPE; + + if (settings->SupportMonitorLayoutPdu) + earlyCapabilityFlags |= RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU; + + if (freerdp_settings_get_bool(settings, FreeRDP_NetworkAutoDetect)) + earlyCapabilityFlags |= RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT; + + if (settings->SupportGraphicsPipeline) + earlyCapabilityFlags |= RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL; + + if (settings->SupportDynamicTimeZone) + earlyCapabilityFlags |= RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE; + + if (settings->SupportHeartbeatPdu) + earlyCapabilityFlags |= RNS_UD_CS_SUPPORT_HEARTBEAT_PDU; + + if (settings->SupportAsymetricKeys) + earlyCapabilityFlags |= RNS_UD_CS_STRONG_ASYMMETRIC_KEYS; + + if (settings->HasRelativeMouseEvent) + earlyCapabilityFlags |= RNS_UD_CS_RELATIVE_MOUSE_INPUT; + + if (settings->SupportSkipChannelJoin) + earlyCapabilityFlags |= RNS_UD_CS_SUPPORT_SKIP_CHANNELJOIN; + + return filterAndLogEarlyClientCapabilityFlags(earlyCapabilityFlags); +} + +static BOOL updateEarlyClientCaps(rdpSettings* settings, UINT32 earlyCapabilityFlags, + UINT32 connectionType) +{ + WINPR_ASSERT(settings); + + if (settings->SupportErrorInfoPdu) + settings->SupportErrorInfoPdu = + (earlyCapabilityFlags & RNS_UD_CS_SUPPORT_ERRINFO_PDU) ? TRUE : FALSE; + + /* RNS_UD_CS_WANT_32BPP_SESSION is already handled in gcc_read_client_core_data: + * + * it is evaluated in combination with highColorDepth and the server side + * settings to determine the session color depth to use. + */ + + if (settings->SupportStatusInfoPdu) + settings->SupportStatusInfoPdu = + (earlyCapabilityFlags & RNS_UD_CS_SUPPORT_STATUSINFO_PDU) ? TRUE : FALSE; + + if (settings->SupportAsymetricKeys) + settings->SupportAsymetricKeys = + (earlyCapabilityFlags & RNS_UD_CS_STRONG_ASYMMETRIC_KEYS) ? TRUE : FALSE; + + if (settings->HasRelativeMouseEvent) + settings->HasRelativeMouseEvent = + (earlyCapabilityFlags & RNS_UD_CS_RELATIVE_MOUSE_INPUT) ? TRUE : FALSE; + + if (settings->NetworkAutoDetect) + settings->NetworkAutoDetect = + (earlyCapabilityFlags & RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT) ? TRUE : FALSE; + + if (settings->SupportSkipChannelJoin) + settings->SupportSkipChannelJoin = + (earlyCapabilityFlags & RNS_UD_CS_SUPPORT_SKIP_CHANNELJOIN) ? TRUE : FALSE; + + if (settings->SupportMonitorLayoutPdu) + settings->SupportMonitorLayoutPdu = + (earlyCapabilityFlags & RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU) ? TRUE : FALSE; + + if (settings->SupportHeartbeatPdu) + settings->SupportHeartbeatPdu = + (earlyCapabilityFlags & RNS_UD_CS_SUPPORT_HEARTBEAT_PDU) ? TRUE : FALSE; + + if (settings->SupportGraphicsPipeline) + settings->SupportGraphicsPipeline = + (earlyCapabilityFlags & RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL) ? TRUE : FALSE; + + if (settings->SupportDynamicTimeZone) + settings->SupportDynamicTimeZone = + (earlyCapabilityFlags & RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE) ? TRUE : FALSE; + + if ((earlyCapabilityFlags & RNS_UD_CS_VALID_CONNECTION_TYPE) == 0) + connectionType = 0; + settings->ConnectionType = connectionType; + + filterAndLogEarlyClientCapabilityFlags(earlyCapabilityFlags); + return TRUE; +} + +static BOOL updateEarlyServerCaps(rdpSettings* settings, UINT32 earlyCapabilityFlags, + UINT32 connectionType) +{ + WINPR_ASSERT(settings); + + settings->SupportEdgeActionV1 = + settings->SupportEdgeActionV1 && + (earlyCapabilityFlags & RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V1) + ? TRUE + : FALSE; + settings->SupportDynamicTimeZone = + settings->SupportDynamicTimeZone && (earlyCapabilityFlags & RNS_UD_SC_DYNAMIC_DST_SUPPORTED) + ? TRUE + : FALSE; + settings->SupportEdgeActionV2 = + settings->SupportEdgeActionV2 && + (earlyCapabilityFlags & RNS_UD_SC_EDGE_ACTIONS_SUPPORTED_V2) + ? TRUE + : FALSE; + settings->SupportSkipChannelJoin = + settings->SupportSkipChannelJoin && + (earlyCapabilityFlags & RNS_UD_SC_SKIP_CHANNELJOIN_SUPPORTED) + ? TRUE + : FALSE; + + filterAndLogEarlyServerCapabilityFlags(earlyCapabilityFlags); + return TRUE; +} + +/** + * Read a client core data block (TS_UD_CS_CORE). + * msdn{cc240510} + * @param s stream + * @param mcs The MCS instance + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_read_client_core_data(wStream* s, rdpMcs* mcs) +{ + char buffer[2048] = { 0 }; + char strbuffer[130] = { 0 }; + UINT32 version = 0; + BYTE connectionType = 0; + UINT32 clientColorDepth = 0; + UINT16 colorDepth = 0; + UINT16 postBeta2ColorDepth = 0; + UINT16 highColorDepth = 0; + UINT32 serverSelectedProtocol = 0; + rdpSettings* settings = mcs_get_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + size_t blockLength = Stream_GetRemainingLength(s); + /* Length of all required fields, until imeFileName */ + if (blockLength < 128) + return FALSE; + + Stream_Read_UINT32(s, version); /* version (4 bytes) */ + settings->RdpVersion = rdp_version_common(version, settings->RdpVersion); + Stream_Read_UINT16(s, settings->DesktopWidth); /* DesktopWidth (2 bytes) */ + Stream_Read_UINT16(s, settings->DesktopHeight); /* DesktopHeight (2 bytes) */ + Stream_Read_UINT16(s, colorDepth); /* ColorDepth (2 bytes) */ + Stream_Seek_UINT16(s); /* SASSequence (Secure Access Sequence) (2 bytes) */ + Stream_Read_UINT32(s, settings->KeyboardLayout); /* KeyboardLayout (4 bytes) */ + Stream_Read_UINT32(s, settings->ClientBuild); /* ClientBuild (4 bytes) */ + + /* clientName (32 bytes, null-terminated unicode, truncated to 15 characters) */ + if (Stream_Read_UTF16_String_As_UTF8_Buffer(s, 32 / sizeof(WCHAR), strbuffer, + ARRAYSIZE(strbuffer)) < 0) + { + WLog_ERR(TAG, "failed to convert client host name"); + return FALSE; + } + + if (!freerdp_settings_set_string(settings, FreeRDP_ClientHostname, strbuffer)) + return FALSE; + + Stream_Read_UINT32(s, settings->KeyboardType); /* KeyboardType (4 bytes) */ + Stream_Read_UINT32(s, settings->KeyboardSubType); /* KeyboardSubType (4 bytes) */ + Stream_Read_UINT32(s, settings->KeyboardFunctionKey); /* KeyboardFunctionKey (4 bytes) */ + Stream_Seek(s, 64); /* imeFileName (64 bytes) */ + blockLength -= 128; + + /** + * The following fields are all optional. If one field is present, all of the preceding + * fields MUST also be present. If one field is not present, all of the subsequent fields + * MUST NOT be present. + * We must check the bytes left before reading each field. + */ + + do + { + UINT16 clientProductIdLen = 0; + if (blockLength < 2) + break; + + Stream_Read_UINT16(s, postBeta2ColorDepth); /* postBeta2ColorDepth (2 bytes) */ + blockLength -= 2; + + if (blockLength < 2) + break; + + Stream_Read_UINT16(s, clientProductIdLen); /* clientProductID (2 bytes) */ + blockLength -= 2; + + if (blockLength < 4) + break; + + Stream_Seek_UINT32(s); /* serialNumber (4 bytes) */ + blockLength -= 4; + + if (blockLength < 2) + break; + + Stream_Read_UINT16(s, highColorDepth); /* highColorDepth (2 bytes) */ + blockLength -= 2; + + if (blockLength < 2) + break; + + Stream_Read_UINT16(s, settings->SupportedColorDepths); /* supportedColorDepths (2 bytes) */ + blockLength -= 2; + + if (blockLength < 2) + break; + + Stream_Read_UINT16(s, settings->EarlyCapabilityFlags); /* earlyCapabilityFlags (2 bytes) */ + blockLength -= 2; + + /* clientDigProductId (64 bytes): Contains a value that uniquely identifies the client */ + + if (blockLength < 64) + break; + + if (Stream_Read_UTF16_String_As_UTF8_Buffer(s, 64 / sizeof(WCHAR), strbuffer, + ARRAYSIZE(strbuffer)) < 0) + { + WLog_ERR(TAG, "failed to convert the client product identifier"); + return FALSE; + } + + if (!freerdp_settings_set_string(settings, FreeRDP_ClientProductId, strbuffer)) + return FALSE; + blockLength -= 64; + + if (blockLength < 1) + break; + + Stream_Read_UINT8(s, connectionType); /* connectionType (1 byte) */ + blockLength -= 1; + + if (blockLength < 1) + break; + + Stream_Seek_UINT8(s); /* pad1octet (1 byte) */ + blockLength -= 1; + + if (blockLength < 4) + break; + + Stream_Read_UINT32(s, serverSelectedProtocol); /* serverSelectedProtocol (4 bytes) */ + blockLength -= 4; + + if (blockLength < 4) + break; + + Stream_Read_UINT32(s, settings->DesktopPhysicalWidth); /* desktopPhysicalWidth (4 bytes) */ + blockLength -= 4; + + if (blockLength < 4) + break; + + Stream_Read_UINT32(s, + settings->DesktopPhysicalHeight); /* desktopPhysicalHeight (4 bytes) */ + blockLength -= 4; + + if (blockLength < 2) + break; + + Stream_Read_UINT16(s, settings->DesktopOrientation); /* desktopOrientation (2 bytes) */ + blockLength -= 2; + + if (blockLength < 4) + break; + + Stream_Read_UINT32(s, settings->DesktopScaleFactor); /* desktopScaleFactor (4 bytes) */ + blockLength -= 4; + + if (blockLength < 4) + break; + + Stream_Read_UINT32(s, settings->DeviceScaleFactor); /* deviceScaleFactor (4 bytes) */ + + if (freerdp_settings_get_bool(settings, FreeRDP_TransportDumpReplay)) + settings->SelectedProtocol = serverSelectedProtocol; + else if (settings->SelectedProtocol != serverSelectedProtocol) + return FALSE; + } while (0); + + if (highColorDepth > 0) + { + if (settings->EarlyCapabilityFlags & RNS_UD_CS_WANT_32BPP_SESSION) + clientColorDepth = 32; + else + clientColorDepth = highColorDepth; + } + else if (postBeta2ColorDepth > 0) + { + switch (postBeta2ColorDepth) + { + case RNS_UD_COLOR_4BPP: + clientColorDepth = 4; + break; + + case RNS_UD_COLOR_8BPP: + clientColorDepth = 8; + break; + + case RNS_UD_COLOR_16BPP_555: + clientColorDepth = 15; + break; + + case RNS_UD_COLOR_16BPP_565: + clientColorDepth = 16; + break; + + case RNS_UD_COLOR_24BPP: + clientColorDepth = 24; + break; + + default: + return FALSE; + } + } + else + { + switch (colorDepth) + { + case RNS_UD_COLOR_4BPP: + clientColorDepth = 4; + break; + + case RNS_UD_COLOR_8BPP: + clientColorDepth = 8; + break; + + default: + return FALSE; + } + } + + /* + * If we are in server mode, accept client's color depth only if + * it is smaller than ours. This is what Windows server does. + */ + if ((clientColorDepth < freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth)) || + !settings->ServerMode) + freerdp_settings_set_uint32(settings, FreeRDP_ColorDepth, clientColorDepth); + + WLog_DBG(TAG, "Received EarlyCapabilityFlags=%s", + rdp_early_client_caps_string(settings->EarlyCapabilityFlags, buffer, sizeof(buffer))); + + return updateEarlyClientCaps(settings, settings->EarlyCapabilityFlags, connectionType); +} + +/** + * Write a client core data block (TS_UD_CS_CORE). + * msdn{cc240510} + * @param s The stream to write to + * @param mcs The MSC instance to get the data from + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_write_client_core_data(wStream* s, const rdpMcs* mcs) +{ + char buffer[2048] = { 0 }; + char dbuffer[2048] = { 0 }; + WCHAR* clientName = NULL; + size_t clientNameLength = 0; + BYTE connectionType = 0; + HIGH_COLOR_DEPTH highColorDepth = HIGH_COLOR_4BPP; + + UINT16 earlyCapabilityFlags = 0; + WCHAR* clientDigProductId = NULL; + size_t clientDigProductIdLength = 0; + const rdpSettings* settings = mcs_get_const_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + const UINT16 SupportedColorDepths = + freerdp_settings_get_uint16(settings, FreeRDP_SupportedColorDepths); + const UINT32 ColorDepth = freerdp_settings_get_uint32(settings, FreeRDP_ColorDepth); + + if (!gcc_write_user_data_header(s, CS_CORE, 234)) + return FALSE; + clientName = ConvertUtf8ToWCharAlloc(settings->ClientHostname, &clientNameLength); + clientDigProductId = + ConvertUtf8ToWCharAlloc(settings->ClientProductId, &clientDigProductIdLength); + + Stream_Write_UINT32(s, settings->RdpVersion); /* Version */ + Stream_Write_UINT16(s, settings->DesktopWidth); /* DesktopWidth */ + Stream_Write_UINT16(s, settings->DesktopHeight); /* DesktopHeight */ + Stream_Write_UINT16(s, + RNS_UD_COLOR_8BPP); /* ColorDepth, ignored because of postBeta2ColorDepth */ + Stream_Write_UINT16(s, RNS_UD_SAS_DEL); /* SASSequence (Secure Access Sequence) */ + Stream_Write_UINT32(s, settings->KeyboardLayout); /* KeyboardLayout */ + Stream_Write_UINT32(s, settings->ClientBuild); /* ClientBuild */ + + /* clientName (32 bytes, null-terminated unicode, truncated to 15 characters) */ + + if (clientNameLength >= 16) + { + clientNameLength = 16; + clientName[clientNameLength - 1] = 0; + } + if (!Stream_EnsureRemainingCapacity(s, 32 + 12 + 64 + 8)) + return FALSE; + + Stream_Write(s, clientName, (clientNameLength * 2)); + Stream_Zero(s, 32 - (clientNameLength * 2)); + free(clientName); + Stream_Write_UINT32(s, settings->KeyboardType); /* KeyboardType */ + Stream_Write_UINT32(s, settings->KeyboardSubType); /* KeyboardSubType */ + Stream_Write_UINT32(s, settings->KeyboardFunctionKey); /* KeyboardFunctionKey */ + Stream_Zero(s, 64); /* imeFileName */ + Stream_Write_UINT16(s, RNS_UD_COLOR_8BPP); /* postBeta2ColorDepth */ + Stream_Write_UINT16(s, 1); /* clientProductID */ + Stream_Write_UINT32(s, 0); /* serialNumber (should be initialized to 0) */ + highColorDepth = ColorDepthToHighColor(ColorDepth); + earlyCapabilityFlags = earlyClientCapsFromSettings(settings); + + connectionType = settings->ConnectionType; + + if (!Stream_EnsureRemainingCapacity(s, 6)) + return FALSE; + + WLog_DBG(TAG, "Sending highColorDepth=%s, supportedColorDepths=%s, earlyCapabilityFlags=%s", + HighColorToString(highColorDepth), + freerdp_supported_color_depths_string(SupportedColorDepths, dbuffer, sizeof(dbuffer)), + rdp_early_client_caps_string(earlyCapabilityFlags, buffer, sizeof(buffer))); + Stream_Write_UINT16(s, highColorDepth); /* highColorDepth */ + Stream_Write_UINT16(s, SupportedColorDepths); /* supportedColorDepths */ + Stream_Write_UINT16(s, earlyCapabilityFlags); /* earlyCapabilityFlags */ + + /* clientDigProductId (64 bytes, null-terminated unicode, truncated to 31 characters) */ + if (clientDigProductIdLength >= 32) + { + clientDigProductIdLength = 32; + clientDigProductId[clientDigProductIdLength - 1] = 0; + } + + if (!Stream_EnsureRemainingCapacity(s, 64 + 24)) + return FALSE; + Stream_Write(s, clientDigProductId, (clientDigProductIdLength * 2)); + Stream_Zero(s, 64 - (clientDigProductIdLength * 2)); + free(clientDigProductId); + Stream_Write_UINT8(s, connectionType); /* connectionType */ + Stream_Write_UINT8(s, 0); /* pad1octet */ + Stream_Write_UINT32(s, settings->SelectedProtocol); /* serverSelectedProtocol */ + Stream_Write_UINT32(s, settings->DesktopPhysicalWidth); /* desktopPhysicalWidth */ + Stream_Write_UINT32(s, settings->DesktopPhysicalHeight); /* desktopPhysicalHeight */ + Stream_Write_UINT16(s, settings->DesktopOrientation); /* desktopOrientation */ + Stream_Write_UINT32(s, settings->DesktopScaleFactor); /* desktopScaleFactor */ + Stream_Write_UINT32(s, settings->DeviceScaleFactor); /* deviceScaleFactor */ + return TRUE; +} + +BOOL gcc_read_server_core_data(wStream* s, rdpMcs* mcs) +{ + UINT32 serverVersion = 0; + rdpSettings* settings = mcs_get_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return FALSE; + + Stream_Read_UINT32(s, serverVersion); /* version */ + settings->RdpVersion = rdp_version_common(serverVersion, settings->RdpVersion); + + if (Stream_GetRemainingLength(s) >= 4) + { + Stream_Read_UINT32(s, settings->RequestedProtocols); /* clientRequestedProtocols */ + } + + if (Stream_GetRemainingLength(s) >= 4) + { + char buffer[2048] = { 0 }; + + Stream_Read_UINT32(s, settings->EarlyCapabilityFlags); /* earlyCapabilityFlags */ + WLog_DBG( + TAG, "Received EarlyCapabilityFlags=%s", + rdp_early_client_caps_string(settings->EarlyCapabilityFlags, buffer, sizeof(buffer))); + } + + return updateEarlyServerCaps(settings, settings->EarlyCapabilityFlags, + settings->ConnectionType); +} + +/* TODO: This function modifies rdpMcs + * TODO: Split this out of this function + */ +BOOL gcc_write_server_core_data(wStream* s, rdpMcs* mcs) +{ + const rdpSettings* settings = mcs_get_const_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + if (!gcc_write_user_data_header(s, SC_CORE, 16)) + return FALSE; + + const UINT32 EarlyCapabilityFlags = earlyServerCapsFromSettings(settings); + Stream_Write_UINT32(s, settings->RdpVersion); /* version (4 bytes) */ + Stream_Write_UINT32(s, settings->RequestedProtocols); /* clientRequestedProtocols (4 bytes) */ + Stream_Write_UINT32(s, EarlyCapabilityFlags); /* earlyCapabilityFlags (4 bytes) */ + return TRUE; +} + +/** + * Read a client security data block (TS_UD_CS_SEC). + * msdn{cc240511} + * @param s stream + * @param mcs MCS instance + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_read_client_security_data(wStream* s, rdpMcs* mcs) +{ + rdpSettings* settings = mcs_get_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + const size_t blockLength = Stream_GetRemainingLength(s); + if (blockLength < 8) + return FALSE; + + if (settings->UseRdpSecurityLayer) + { + Stream_Read_UINT32(s, settings->EncryptionMethods); /* encryptionMethods */ + + if (settings->EncryptionMethods == ENCRYPTION_METHOD_NONE) + Stream_Read_UINT32(s, settings->EncryptionMethods); /* extEncryptionMethods */ + else + Stream_Seek(s, 4); + } + else + { + Stream_Seek(s, 8); + } + + return TRUE; +} + +/** + * Write a client security data block (TS_UD_CS_SEC). + * msdn{cc240511} + * @param s stream + * @param mcs The MCS instance + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_write_client_security_data(wStream* s, const rdpMcs* mcs) +{ + const rdpSettings* settings = mcs_get_const_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + if (!gcc_write_user_data_header(s, CS_SECURITY, 12)) + return FALSE; + + if (settings->UseRdpSecurityLayer) + { + Stream_Write_UINT32(s, settings->EncryptionMethods); /* encryptionMethods */ + Stream_Write_UINT32(s, 0); /* extEncryptionMethods */ + } + else + { + /* French locale, disable encryption */ + Stream_Write_UINT32(s, 0); /* encryptionMethods */ + Stream_Write_UINT32(s, settings->EncryptionMethods); /* extEncryptionMethods */ + } + return TRUE; +} + +BOOL gcc_read_server_security_data(wStream* s, rdpMcs* mcs) +{ + const BYTE* data = NULL; + UINT32 length = 0; + BOOL validCryptoConfig = FALSE; + UINT32 EncryptionMethod = 0; + UINT32 EncryptionLevel = 0; + rdpSettings* settings = mcs_get_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return FALSE; + + Stream_Read_UINT32(s, EncryptionMethod); /* encryptionMethod */ + Stream_Read_UINT32(s, EncryptionLevel); /* encryptionLevel */ + + /* Only accept valid/known encryption methods */ + switch (EncryptionMethod) + { + case ENCRYPTION_METHOD_NONE: + WLog_DBG(TAG, "Server rdp encryption method: NONE"); + break; + + case ENCRYPTION_METHOD_40BIT: + WLog_DBG(TAG, "Server rdp encryption method: 40BIT"); + break; + + case ENCRYPTION_METHOD_56BIT: + WLog_DBG(TAG, "Server rdp encryption method: 56BIT"); + break; + + case ENCRYPTION_METHOD_128BIT: + WLog_DBG(TAG, "Server rdp encryption method: 128BIT"); + break; + + case ENCRYPTION_METHOD_FIPS: + WLog_DBG(TAG, "Server rdp encryption method: FIPS"); + break; + + default: + WLog_ERR(TAG, "Received unknown encryption method %08" PRIX32 "", EncryptionMethod); + return FALSE; + } + + if (settings->UseRdpSecurityLayer && !(settings->EncryptionMethods & EncryptionMethod)) + { + WLog_WARN(TAG, "Server uses non-advertised encryption method 0x%08" PRIX32 "", + EncryptionMethod); + /* FIXME: Should we return FALSE; in this case ?? */ + } + + settings->EncryptionMethods = EncryptionMethod; + settings->EncryptionLevel = EncryptionLevel; + /* Verify encryption level/method combinations according to MS-RDPBCGR Section 5.3.2 */ + switch (settings->EncryptionLevel) + { + case ENCRYPTION_LEVEL_NONE: + if (settings->EncryptionMethods == ENCRYPTION_METHOD_NONE) + { + validCryptoConfig = TRUE; + } + + break; + + case ENCRYPTION_LEVEL_FIPS: + if (settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS) + { + validCryptoConfig = TRUE; + } + + break; + + case ENCRYPTION_LEVEL_LOW: + case ENCRYPTION_LEVEL_HIGH: + case ENCRYPTION_LEVEL_CLIENT_COMPATIBLE: + if (settings->EncryptionMethods == ENCRYPTION_METHOD_40BIT || + settings->EncryptionMethods == ENCRYPTION_METHOD_56BIT || + settings->EncryptionMethods == ENCRYPTION_METHOD_128BIT || + settings->EncryptionMethods == ENCRYPTION_METHOD_FIPS) + { + validCryptoConfig = TRUE; + } + + break; + + default: + WLog_ERR(TAG, "Received unknown encryption level 0x%08" PRIX32 "", + settings->EncryptionLevel); + } + + if (!validCryptoConfig) + { + WLog_ERR(TAG, + "Received invalid cryptographic configuration (level=0x%08" PRIX32 + " method=0x%08" PRIX32 ")", + settings->EncryptionLevel, settings->EncryptionMethods); + return FALSE; + } + + if (settings->EncryptionLevel == ENCRYPTION_LEVEL_NONE) + { + /* serverRandomLen and serverCertLen must not be present */ + settings->UseRdpSecurityLayer = FALSE; + return TRUE; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 8)) + return FALSE; + + Stream_Read_UINT32(s, settings->ServerRandomLength); /* serverRandomLen */ + Stream_Read_UINT32(s, settings->ServerCertificateLength); /* serverCertLen */ + + if ((settings->ServerRandomLength == 0) || (settings->ServerCertificateLength == 0)) + return FALSE; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, settings->ServerRandomLength)) + return FALSE; + + /* serverRandom */ + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ServerRandom, NULL, + settings->ServerRandomLength)) + goto fail; + + Stream_Read(s, settings->ServerRandom, settings->ServerRandomLength); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, settings->ServerCertificateLength)) + goto fail; + + /* serverCertificate */ + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ServerCertificate, NULL, + settings->ServerCertificateLength)) + goto fail; + + Stream_Read(s, settings->ServerCertificate, settings->ServerCertificateLength); + + data = settings->ServerCertificate; + length = settings->ServerCertificateLength; + + if (!freerdp_certificate_read_server_cert(settings->RdpServerCertificate, data, length)) + goto fail; + + return TRUE; +fail: + free(settings->ServerRandom); + free(settings->ServerCertificate); + settings->ServerRandom = NULL; + settings->ServerCertificate = NULL; + return FALSE; +} + +static BOOL gcc_update_server_random(rdpSettings* settings) +{ + const size_t length = 32; + WINPR_ASSERT(settings); + if (!freerdp_settings_set_pointer_len(settings, FreeRDP_ServerRandom, NULL, length)) + return FALSE; + BYTE* data = freerdp_settings_get_pointer_writable(settings, FreeRDP_ServerRandom); + if (!data) + return FALSE; + winpr_RAND(data, length); + return TRUE; +} + +/* TODO: This function does manipulate data in rdpMcs + * TODO: Split this out of this function + */ +BOOL gcc_write_server_security_data(wStream* s, rdpMcs* mcs) +{ + const rdpSettings* settings = mcs_get_const_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + const size_t posHeader = Stream_GetPosition(s); + if (!gcc_write_user_data_header(s, SC_SECURITY, 12)) + return FALSE; + + Stream_Write_UINT32(s, settings->EncryptionMethods); /* encryptionMethod */ + Stream_Write_UINT32(s, settings->EncryptionLevel); /* encryptionLevel */ + + if (settings->EncryptionMethods == ENCRYPTION_METHOD_NONE) + return TRUE; + if (!gcc_update_server_random(settings)) + return FALSE; + + if (!Stream_EnsureRemainingCapacity(s, sizeof(UINT32) + settings->ServerRandomLength)) + return FALSE; + Stream_Write_UINT32(s, settings->ServerRandomLength); /* serverRandomLen */ + const size_t posCertLen = Stream_GetPosition(s); + Stream_Seek_UINT32(s); /* serverCertLen */ + Stream_Write(s, settings->ServerRandom, settings->ServerRandomLength); + + const SSIZE_T len = freerdp_certificate_write_server_cert( + settings->RdpServerCertificate, CERT_TEMPORARILY_ISSUED | CERT_CHAIN_VERSION_1, s); + if (len < 0) + return FALSE; + const size_t end = Stream_GetPosition(s); + Stream_SetPosition(s, posHeader); + if (!gcc_write_user_data_header(s, SC_SECURITY, end - posHeader)) + return FALSE; + Stream_SetPosition(s, posCertLen); + WINPR_ASSERT(len <= UINT32_MAX); + Stream_Write_UINT32(s, (UINT32)len); + Stream_SetPosition(s, end); + return TRUE; +} + +/** + * Read a client network data block (TS_UD_CS_NET). + * msdn{cc240512} + * + * @param s stream + * @param mcs The MCS instance + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_read_client_network_data(wStream* s, rdpMcs* mcs) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(mcs); + + const size_t blockLength = Stream_GetRemainingLength(s); + if (blockLength < 4) + return FALSE; + + Stream_Read_UINT32(s, mcs->channelCount); /* channelCount */ + + if (blockLength < 4 + mcs->channelCount * 12) + return FALSE; + + if (mcs->channelCount > CHANNEL_MAX_COUNT) + return FALSE; + + /* channelDefArray */ + for (UINT32 i = 0; i < mcs->channelCount; i++) + { + /** + * CHANNEL_DEF + * - name: an 8-byte array containing a null-terminated collection + * of seven ANSI characters that uniquely identify the channel. + * - options: a 32-bit, unsigned integer. Channel option flags + */ + rdpMcsChannel* channel = &mcs->channels[i]; + Stream_Read(s, channel->Name, CHANNEL_NAME_LEN + 1); /* name (8 bytes) */ + + if (!memchr(channel->Name, 0, CHANNEL_NAME_LEN + 1)) + { + WLog_ERR( + TAG, + "protocol violation: received a static channel name with missing null-termination"); + return FALSE; + } + + Stream_Read_UINT32(s, channel->options); /* options (4 bytes) */ + channel->ChannelId = mcs->baseChannelId++; + } + + return TRUE; +} + +/** + * Write a client network data block (TS_UD_CS_NET). + * msdn{cc240512} + * @param s stream + * @param mcs The MCS to use + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_write_client_network_data(wStream* s, const rdpMcs* mcs) +{ + UINT16 length = 0; + WINPR_ASSERT(s); + WINPR_ASSERT(mcs); + if (mcs->channelCount > 0) + { + length = mcs->channelCount * 12 + 8; + if (!gcc_write_user_data_header(s, CS_NET, length)) + return FALSE; + Stream_Write_UINT32(s, mcs->channelCount); /* channelCount */ + + /* channelDefArray */ + for (UINT32 i = 0; i < mcs->channelCount; i++) + { + /* CHANNEL_DEF */ + rdpMcsChannel* channel = &mcs->channels[i]; + Stream_Write(s, channel->Name, CHANNEL_NAME_LEN + 1); /* name (8 bytes) */ + Stream_Write_UINT32(s, channel->options); /* options (4 bytes) */ + } + } + return TRUE; +} + +BOOL gcc_read_server_network_data(wStream* s, rdpMcs* mcs) +{ + UINT16 channelId = 0; + UINT16 MCSChannelId = 0; + UINT16 channelCount = 0; + UINT32 parsedChannelCount = 0; + WINPR_ASSERT(s); + WINPR_ASSERT(mcs); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return FALSE; + + Stream_Read_UINT16(s, MCSChannelId); /* MCSChannelId */ + Stream_Read_UINT16(s, channelCount); /* channelCount */ + parsedChannelCount = channelCount; + + if (channelCount != mcs->channelCount) + { + WLog_ERR(TAG, "requested %" PRIu32 " channels, got %" PRIu16 " instead", mcs->channelCount, + channelCount); + + /* we ensure that the response is not bigger than the request */ + + mcs->channelCount = channelCount; + } + + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, channelCount, 2ull)) + return FALSE; + + for (UINT32 i = 0; i < parsedChannelCount; i++) + { + rdpMcsChannel* channel = &mcs->channels[i]; + Stream_Read_UINT16(s, channelId); /* channelId */ + channel->ChannelId = channelId; + } + + if (channelCount % 2 == 1) + return Stream_SafeSeek(s, 2); /* padding */ + + return TRUE; +} + +BOOL gcc_write_server_network_data(wStream* s, const rdpMcs* mcs) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(mcs); + const size_t payloadLen = 8 + mcs->channelCount * 2 + (mcs->channelCount % 2 == 1 ? 2 : 0); + + if (!gcc_write_user_data_header(s, SC_NET, payloadLen)) + return FALSE; + + Stream_Write_UINT16(s, MCS_GLOBAL_CHANNEL_ID); /* MCSChannelId */ + Stream_Write_UINT16(s, mcs->channelCount); /* channelCount */ + + for (UINT32 i = 0; i < mcs->channelCount; i++) + { + const rdpMcsChannel* channel = &mcs->channels[i]; + Stream_Write_UINT16(s, channel->ChannelId); + } + + if (mcs->channelCount % 2 == 1) + Stream_Write_UINT16(s, 0); + + return TRUE; +} + +/** + * Read a client cluster data block (TS_UD_CS_CLUSTER). + * msdn{cc240514} + * @param s stream + * @param mcs The MCS instance + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_read_client_cluster_data(wStream* s, rdpMcs* mcs) +{ + char buffer[128] = { 0 }; + UINT32 redirectedSessionId = 0; + rdpSettings* settings = mcs_get_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + const size_t blockLength = Stream_GetRemainingLength(s); + if (blockLength < 8) + return FALSE; + + Stream_Read_UINT32(s, settings->ClusterInfoFlags); /* flags */ + Stream_Read_UINT32(s, redirectedSessionId); /* redirectedSessionId */ + + WLog_VRB(TAG, "read ClusterInfoFlags=%s, RedirectedSessionId=0x%08" PRIx32, + rdp_cluster_info_flags_to_string(settings->ClusterInfoFlags, buffer, sizeof(buffer)), + redirectedSessionId); + if (settings->ClusterInfoFlags & REDIRECTED_SESSIONID_FIELD_VALID) + settings->RedirectedSessionId = redirectedSessionId; + + settings->ConsoleSession = + (settings->ClusterInfoFlags & REDIRECTED_SESSIONID_FIELD_VALID) ? TRUE : FALSE; + settings->RedirectSmartCards = + (settings->ClusterInfoFlags & REDIRECTED_SMARTCARD) ? TRUE : FALSE; + + if (blockLength != 8) + { + if (Stream_GetRemainingLength(s) >= (size_t)(blockLength - 8)) + { + /* The old Microsoft Mac RDP client can send a pad here */ + Stream_Seek(s, (blockLength - 8)); + } + } + + return TRUE; +} + +/** + * Write a client cluster data block (TS_UD_CS_CLUSTER). + * msdn{cc240514} + * @param s stream + * @param mcs The MCS instance + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_write_client_cluster_data(wStream* s, const rdpMcs* mcs) +{ + char buffer[128] = { 0 }; + UINT32 flags = 0; + const rdpSettings* settings = mcs_get_const_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + if (!gcc_write_user_data_header(s, CS_CLUSTER, 12)) + return FALSE; + flags = settings->ClusterInfoFlags; + + if (settings->ConsoleSession || settings->RedirectedSessionId) + flags |= REDIRECTED_SESSIONID_FIELD_VALID; + + if (settings->RedirectSmartCards && settings->SmartcardLogon) + flags |= REDIRECTED_SMARTCARD; + + if (flags & REDIRECTION_SUPPORTED) + { + /* REDIRECTION_VERSION6 requires multitransport enabled. + * if we run without that use REDIRECTION_VERSION5 */ + if (freerdp_settings_get_bool(settings, FreeRDP_SupportMultitransport)) + flags |= (REDIRECTION_VERSION6 << 2); + else + flags |= (REDIRECTION_VERSION5 << 2); + } + + WLog_VRB(TAG, "write ClusterInfoFlags=%s, RedirectedSessionId=0x%08" PRIx32, + rdp_cluster_info_flags_to_string(flags, buffer, sizeof(buffer)), + settings->RedirectedSessionId); + Stream_Write_UINT32(s, flags); /* flags */ + Stream_Write_UINT32(s, settings->RedirectedSessionId); /* redirectedSessionID */ + return TRUE; +} + +/** + * Read a client monitor data block (TS_UD_CS_MONITOR). + * msdn{dd305336} + * @param s stream + * @param mcs The MCS instance + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_read_client_monitor_data(wStream* s, rdpMcs* mcs) +{ + UINT32 monitorCount = 0; + rdpSettings* settings = mcs_get_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + const size_t blockLength = Stream_GetRemainingLength(s); + if (blockLength < 8) + return FALSE; + + Stream_Read_UINT32(s, settings->MonitorFlags); /* flags */ + Stream_Read_UINT32(s, monitorCount); /* monitorCount */ + + /* 2.2.1.3.6 Client Monitor Data - + * monitorCount (4 bytes): A 32-bit, unsigned integer. The number of display + * monitor definitions in the monitorDefArray field (the maximum allowed is 16). + */ + if (monitorCount > 16) + { + WLog_ERR(TAG, "announced monitors(%" PRIu32 ") exceed the 16 limit", monitorCount); + return FALSE; + } + + if (monitorCount > settings->MonitorDefArraySize) + { + WLog_ERR(TAG, "too many announced monitors(%" PRIu32 "), clamping to %" PRIu32 "", + monitorCount, settings->MonitorDefArraySize); + monitorCount = settings->MonitorDefArraySize; + } + + if ((UINT32)((blockLength - 8) / 20) < monitorCount) + return FALSE; + + settings->MonitorCount = monitorCount; + + for (UINT32 index = 0; index < monitorCount; index++) + { + UINT32 left = 0; + UINT32 top = 0; + UINT32 right = 0; + UINT32 bottom = 0; + UINT32 flags = 0; + rdpMonitor* current = &settings->MonitorDefArray[index]; + + Stream_Read_UINT32(s, left); /* left */ + Stream_Read_UINT32(s, top); /* top */ + Stream_Read_UINT32(s, right); /* right */ + Stream_Read_UINT32(s, bottom); /* bottom */ + Stream_Read_UINT32(s, flags); /* flags */ + current->x = left; + current->y = top; + current->width = right - left + 1; + current->height = bottom - top + 1; + current->is_primary = (flags & MONITOR_PRIMARY) ? TRUE : FALSE; + } + + return TRUE; +} + +/** + * Write a client monitor data block (TS_UD_CS_MONITOR). + * msdn{dd305336} + * @param s stream + * @param mcs The MCS to use + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_write_client_monitor_data(wStream* s, const rdpMcs* mcs) +{ + UINT16 length = 0; + INT32 baseX = 0; + INT32 baseY = 0; + const rdpSettings* settings = mcs_get_const_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + WLog_DBG(TAG, "MonitorCount=%" PRIu32, settings->MonitorCount); + if (settings->MonitorCount > 1) + { + length = (20 * settings->MonitorCount) + 12; + if (!gcc_write_user_data_header(s, CS_MONITOR, length)) + return FALSE; + Stream_Write_UINT32(s, settings->MonitorFlags); /* flags */ + Stream_Write_UINT32(s, settings->MonitorCount); /* monitorCount */ + + /* first pass to get the primary monitor coordinates (it is supposed to be + * in (0,0) */ + for (UINT32 i = 0; i < settings->MonitorCount; i++) + { + const rdpMonitor* current = &settings->MonitorDefArray[i]; + if (current->is_primary) + { + baseX = current->x; + baseY = current->y; + break; + } + } + + for (UINT32 i = 0; i < settings->MonitorCount; i++) + { + const rdpMonitor* current = &settings->MonitorDefArray[i]; + const UINT32 left = current->x - baseX; + const UINT32 top = current->y - baseY; + const UINT32 right = left + current->width - 1; + const UINT32 bottom = top + current->height - 1; + const UINT32 flags = current->is_primary ? MONITOR_PRIMARY : 0; + WLog_DBG(TAG, + "Monitor[%" PRIu32 "]: top=%" PRIu32 ", left=%" PRIu32 ", bottom=%" PRIu32 + ", right=%" PRIu32 ", flags=%" PRIu32, + i, top, left, bottom, right, flags); + Stream_Write_UINT32(s, left); /* left */ + Stream_Write_UINT32(s, top); /* top */ + Stream_Write_UINT32(s, right); /* right */ + Stream_Write_UINT32(s, bottom); /* bottom */ + Stream_Write_UINT32(s, flags); /* flags */ + } + } + WLog_DBG(TAG, "FINISHED"); + return TRUE; +} + +BOOL gcc_read_client_monitor_extended_data(wStream* s, rdpMcs* mcs) +{ + UINT32 monitorCount = 0; + UINT32 monitorAttributeSize = 0; + rdpSettings* settings = mcs_get_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + const size_t blockLength = Stream_GetRemainingLength(s); + if (blockLength < 12) + return FALSE; + + Stream_Read_UINT32(s, settings->MonitorAttributeFlags); /* flags */ + Stream_Read_UINT32(s, monitorAttributeSize); /* monitorAttributeSize */ + Stream_Read_UINT32(s, monitorCount); /* monitorCount */ + + if (monitorAttributeSize != 20) + return FALSE; + + if ((blockLength - 12) / monitorAttributeSize < monitorCount) + return FALSE; + + if (settings->MonitorCount != monitorCount) + return FALSE; + + settings->HasMonitorAttributes = TRUE; + + for (UINT32 index = 0; index < monitorCount; index++) + { + rdpMonitor* current = &settings->MonitorDefArray[index]; + Stream_Read_UINT32(s, current->attributes.physicalWidth); /* physicalWidth */ + Stream_Read_UINT32(s, current->attributes.physicalHeight); /* physicalHeight */ + Stream_Read_UINT32(s, current->attributes.orientation); /* orientation */ + Stream_Read_UINT32(s, current->attributes.desktopScaleFactor); /* desktopScaleFactor */ + Stream_Read_UINT32(s, current->attributes.deviceScaleFactor); /* deviceScaleFactor */ + } + + return TRUE; +} + +BOOL gcc_write_client_monitor_extended_data(wStream* s, const rdpMcs* mcs) +{ + UINT16 length = 0; + const rdpSettings* settings = mcs_get_const_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + if (settings->HasMonitorAttributes) + { + length = (20 * settings->MonitorCount) + 16; + if (!gcc_write_user_data_header(s, CS_MONITOR_EX, length)) + return FALSE; + Stream_Write_UINT32(s, settings->MonitorAttributeFlags); /* flags */ + Stream_Write_UINT32(s, 20); /* monitorAttributeSize */ + Stream_Write_UINT32(s, settings->MonitorCount); /* monitorCount */ + + for (UINT32 i = 0; i < settings->MonitorCount; i++) + { + const rdpMonitor* current = &settings->MonitorDefArray[i]; + Stream_Write_UINT32(s, current->attributes.physicalWidth); /* physicalWidth */ + Stream_Write_UINT32(s, current->attributes.physicalHeight); /* physicalHeight */ + Stream_Write_UINT32(s, current->attributes.orientation); /* orientation */ + Stream_Write_UINT32(s, current->attributes.desktopScaleFactor); /* desktopScaleFactor */ + Stream_Write_UINT32(s, current->attributes.deviceScaleFactor); /* deviceScaleFactor */ + } + } + return TRUE; +} + +/** + * Read a client message channel data block (TS_UD_CS_MCS_MSGCHANNEL). + * msdn{jj217627} + * @param s stream + * @param mcs The MCS instance + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_read_client_message_channel_data(wStream* s, rdpMcs* mcs) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(mcs); + + const size_t blockLength = Stream_GetRemainingLength(s); + if (blockLength < 4) + return FALSE; + + Stream_Read_UINT32(s, mcs->flags); + mcs->messageChannelId = mcs->baseChannelId++; + return TRUE; +} + +/** + * Write a client message channel data block (TS_UD_CS_MCS_MSGCHANNEL). + * msdn{jj217627} + * @param s stream + * @param mcs The MCS instance + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_write_client_message_channel_data(wStream* s, const rdpMcs* mcs) +{ + const rdpSettings* settings = mcs_get_const_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(mcs); + WINPR_ASSERT(settings); + if (freerdp_settings_get_bool(settings, FreeRDP_NetworkAutoDetect) || + settings->SupportHeartbeatPdu || settings->SupportMultitransport) + { + if (!gcc_write_user_data_header(s, CS_MCS_MSGCHANNEL, 8)) + return FALSE; + Stream_Write_UINT32(s, mcs->flags); /* flags */ + } + return TRUE; +} + +BOOL gcc_read_server_message_channel_data(wStream* s, rdpMcs* mcs) +{ + UINT16 MCSChannelId = 0; + WINPR_ASSERT(s); + WINPR_ASSERT(mcs); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return FALSE; + + Stream_Read_UINT16(s, MCSChannelId); /* MCSChannelId */ + /* Save the MCS message channel id */ + mcs->messageChannelId = MCSChannelId; + return TRUE; +} + +BOOL gcc_write_server_message_channel_data(wStream* s, const rdpMcs* mcs) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(mcs); + if (mcs->messageChannelId == 0) + return TRUE; + + if (!gcc_write_user_data_header(s, SC_MCS_MSGCHANNEL, 6)) + return FALSE; + + Stream_Write_UINT16(s, mcs->messageChannelId); /* mcsChannelId (2 bytes) */ + return TRUE; +} + +/** + * Read a client multitransport channel data block (TS_UD_CS_MULTITRANSPORT). + * msdn{jj217498} + * @param s stream + * @param mcs The MCS instance + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_read_client_multitransport_channel_data(wStream* s, rdpMcs* mcs) +{ + rdpSettings* settings = mcs_get_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + const size_t blockLength = Stream_GetRemainingLength(s); + if (blockLength < 4) + return FALSE; + + UINT32 remoteFlags = 0; + Stream_Read_UINT32(s, remoteFlags); + settings->MultitransportFlags &= remoteFlags; /* merge local and remote flags */ + return TRUE; +} + +/** + * Write a client multitransport channel data block (TS_UD_CS_MULTITRANSPORT). + * msdn{jj217498} + * + * @param s stream + * @param mcs The MCS instance + * + * @return \b TRUE for success, \b FALSE otherwise + */ + +BOOL gcc_write_client_multitransport_channel_data(wStream* s, const rdpMcs* mcs) +{ + const rdpSettings* settings = mcs_get_const_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + if (!gcc_write_user_data_header(s, CS_MULTITRANSPORT, 8)) + return FALSE; + Stream_Write_UINT32(s, settings->MultitransportFlags); /* flags */ + return TRUE; +} + +BOOL gcc_read_server_multitransport_channel_data(wStream* s, rdpMcs* mcs) +{ + rdpSettings* settings = mcs_get_settings(mcs); + UINT32 remoteFlags = 0; + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return FALSE; + + Stream_Read_UINT32(s, remoteFlags); + settings->MultitransportFlags &= remoteFlags; /* merge with client setting */ + return TRUE; +} + +BOOL gcc_write_server_multitransport_channel_data(wStream* s, const rdpMcs* mcs) +{ + const rdpSettings* settings = mcs_get_const_settings(mcs); + + WINPR_ASSERT(s); + WINPR_ASSERT(settings); + + if (!gcc_write_user_data_header(s, SC_MULTITRANSPORT, 8)) + return FALSE; + + Stream_Write_UINT32(s, settings->MultitransportFlags); /* flags (4 bytes) */ + return TRUE; +} |