22#include <freerdp/config.h>
25#include <winpr/assert.h>
26#include <winpr/print.h>
28#include <freerdp/freerdp.h>
29#include <freerdp/assistance.h>
31#include <freerdp/channels/log.h>
32#include <freerdp/client/remdesk.h>
34#include "remdesk_main.h"
35#include "remdesk_common.h"
48 WLog_ERR(TAG,
"remdesk was null!");
50 return CHANNEL_RC_INVALID_INSTANCE;
53 WINPR_ASSERT(remdesk->channelEntryPoints.pVirtualChannelWriteEx);
54 status = remdesk->channelEntryPoints.pVirtualChannelWriteEx(
55 remdesk->InitHandle, remdesk->OpenHandle, Stream_Buffer(s), (UINT32)Stream_Length(s), s);
57 if (status != CHANNEL_RC_OK)
60 WLog_ERR(TAG,
"pVirtualChannelWriteEx failed with %s [%08" PRIX32
"]",
61 WTSErrorToString(status), status);
71static UINT remdesk_generate_expert_blob(
remdeskPlugin* remdesk)
73 const char* name = NULL;
75 const char* password = NULL;
76 rdpSettings* settings = NULL;
78 WINPR_ASSERT(remdesk);
80 WINPR_ASSERT(remdesk->rdpcontext);
81 settings = remdesk->rdpcontext->settings;
82 WINPR_ASSERT(settings);
84 if (remdesk->ExpertBlob)
93 WLog_ERR(TAG,
"password was not set!");
94 return ERROR_INTERNAL_ERROR;
103 remdesk->EncryptedPassStub =
104 freerdp_assistance_encrypt_pass_stub(password, stub, &(remdesk->EncryptedPassStubSize));
106 if (!remdesk->EncryptedPassStub)
108 WLog_ERR(TAG,
"freerdp_assistance_encrypt_pass_stub failed!");
109 return ERROR_INTERNAL_ERROR;
112 pass = freerdp_assistance_bin_to_hex_string(remdesk->EncryptedPassStub,
113 remdesk->EncryptedPassStubSize);
117 WLog_ERR(TAG,
"freerdp_assistance_bin_to_hex_string failed!");
118 return ERROR_INTERNAL_ERROR;
121 remdesk->ExpertBlob = freerdp_assistance_construct_expert_blob(name, pass);
124 if (!remdesk->ExpertBlob)
126 WLog_ERR(TAG,
"freerdp_assistance_construct_expert_blob failed!");
127 return ERROR_INTERNAL_ERROR;
130 return CHANNEL_RC_OK;
138static UINT remdesk_recv_ctl_server_announce_pdu(WINPR_ATTR_UNUSED
remdeskPlugin* remdesk,
142 WINPR_ASSERT(remdesk);
144 WINPR_ASSERT(header);
146 WLog_ERR(
"TODO",
"TODO: implement");
147 return CHANNEL_RC_OK;
158 UINT32 versionMajor = 0;
159 UINT32 versionMinor = 0;
161 WINPR_ASSERT(remdesk);
163 WINPR_ASSERT(header);
165 if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
166 return ERROR_INVALID_DATA;
168 Stream_Read_UINT32(s, versionMajor);
169 Stream_Read_UINT32(s, versionMinor);
171 if ((versionMajor != 1) || (versionMinor > 2) || (versionMinor == 0))
173 WLog_ERR(TAG,
"Unsupported protocol version %" PRId32
".%" PRId32, versionMajor,
177 remdesk->Version = versionMinor;
178 return CHANNEL_RC_OK;
186static UINT remdesk_send_ctl_version_info_pdu(
remdeskPlugin* remdesk)
190 WINPR_ASSERT(remdesk);
192 UINT error = remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_VERSIONINFO, 8);
196 pdu.versionMajor = 1;
197 pdu.versionMinor = 2;
198 wStream* s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength);
202 WLog_ERR(TAG,
"Stream_New failed!");
203 return CHANNEL_RC_NO_MEMORY;
206 error = remdesk_write_ctl_header(s, &(pdu.ctlHeader));
209 Stream_Free(s, TRUE);
212 Stream_Write_UINT32(s, pdu.versionMajor);
213 Stream_Write_UINT32(s, pdu.versionMinor);
214 Stream_SealLength(s);
216 if ((error = remdesk_virtual_channel_write(remdesk, s)))
217 WLog_ERR(TAG,
"remdesk_virtual_channel_write failed with error %" PRIu32
"!", error);
233 WINPR_ASSERT(remdesk);
235 WINPR_ASSERT(header);
236 WINPR_ASSERT(pResult);
238 if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
239 return ERROR_INVALID_DATA;
241 Stream_Read_UINT32(s, result);
246 case REMDESK_ERROR_HELPEESAIDNO:
247 WLog_DBG(TAG,
"remote assistance connection request was denied");
248 return ERROR_CONNECTION_REFUSED;
254 return CHANNEL_RC_OK;
262static UINT remdesk_send_ctl_authenticate_pdu(
remdeskPlugin* remdesk)
264 UINT error = ERROR_INTERNAL_ERROR;
265 size_t cbExpertBlobW = 0;
266 WCHAR* expertBlobW = NULL;
267 size_t cbRaConnectionStringW = 0;
270 WINPR_ASSERT(remdesk);
272 if ((error = remdesk_generate_expert_blob(remdesk)))
274 WLog_ERR(TAG,
"remdesk_generate_expert_blob failed with error %" PRIu32
"", error);
278 const char* expertBlob = remdesk->ExpertBlob;
279 WINPR_ASSERT(remdesk->rdpcontext);
280 rdpSettings* settings = remdesk->rdpcontext->settings;
281 WINPR_ASSERT(settings);
283 const char* raConnectionString =
285 WCHAR* raConnectionStringW =
286 ConvertUtf8ToWCharAlloc(raConnectionString, &cbRaConnectionStringW);
288 if (!raConnectionStringW || (cbRaConnectionStringW > UINT32_MAX /
sizeof(WCHAR)))
291 cbRaConnectionStringW = cbRaConnectionStringW *
sizeof(WCHAR);
293 expertBlobW = ConvertUtf8ToWCharAlloc(expertBlob, &cbExpertBlobW);
298 cbExpertBlobW = cbExpertBlobW *
sizeof(WCHAR);
299 error = remdesk_prepare_ctl_header(&(ctlHeader), REMDESK_CTL_AUTHENTICATE,
300 cbRaConnectionStringW + cbExpertBlobW);
305 wStream* s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + ctlHeader.ch.DataLength);
308 WLog_ERR(TAG,
"Stream_New failed!");
309 error = CHANNEL_RC_NO_MEMORY;
313 error = remdesk_write_ctl_header(s, &ctlHeader);
316 Stream_Free(s, TRUE);
319 Stream_Write(s, raConnectionStringW, cbRaConnectionStringW);
320 Stream_Write(s, expertBlobW, cbExpertBlobW);
321 Stream_SealLength(s);
323 error = remdesk_virtual_channel_write(remdesk, s);
326 WLog_ERR(TAG,
"remdesk_virtual_channel_write failed with error %" PRIu32
"!", error);
329 free(raConnectionStringW);
340static UINT remdesk_send_ctl_remote_control_desktop_pdu(
remdeskPlugin* remdesk)
345 WINPR_ASSERT(remdesk);
346 WINPR_ASSERT(remdesk->rdpcontext);
347 rdpSettings* settings = remdesk->rdpcontext->settings;
348 WINPR_ASSERT(settings);
350 const char* raConnectionString =
352 WCHAR* raConnectionStringW = ConvertUtf8ToWCharAlloc(raConnectionString, &length);
353 size_t cbRaConnectionStringW = length *
sizeof(WCHAR);
355 if (!raConnectionStringW)
356 return ERROR_INTERNAL_ERROR;
359 error = remdesk_prepare_ctl_header(&ctlHeader, REMDESK_CTL_REMOTE_CONTROL_DESKTOP,
360 cbRaConnectionStringW);
361 if (error != CHANNEL_RC_OK)
365 wStream* s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + ctlHeader.ch.DataLength);
369 WLog_ERR(TAG,
"Stream_New failed!");
370 error = CHANNEL_RC_NO_MEMORY;
374 error = remdesk_write_ctl_header(s, &ctlHeader);
377 Stream_Free(s, TRUE);
380 Stream_Write(s, raConnectionStringW, cbRaConnectionStringW);
381 Stream_SealLength(s);
383 if ((error = remdesk_virtual_channel_write(remdesk, s)))
384 WLog_ERR(TAG,
"remdesk_virtual_channel_write failed with error %" PRIu32
"!", error);
388 free(raConnectionStringW);
398static UINT remdesk_send_ctl_verify_password_pdu(
remdeskPlugin* remdesk)
400 size_t cbExpertBlobW = 0;
403 WINPR_ASSERT(remdesk);
405 UINT error = remdesk_generate_expert_blob(remdesk);
408 WLog_ERR(TAG,
"remdesk_generate_expert_blob failed with error %" PRIu32
"!", error);
412 pdu.expertBlob = remdesk->ExpertBlob;
413 WCHAR* expertBlobW = ConvertUtf8ToWCharAlloc(pdu.expertBlob, &cbExpertBlobW);
418 cbExpertBlobW = cbExpertBlobW *
sizeof(WCHAR);
420 remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_VERIFY_PASSWORD, cbExpertBlobW);
426 Stream_New(NULL, 1ULL * REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength);
430 WLog_ERR(TAG,
"Stream_New failed!");
431 error = CHANNEL_RC_NO_MEMORY;
435 error = remdesk_write_ctl_header(s, &(pdu.ctlHeader));
438 Stream_Free(s, TRUE);
441 Stream_Write(s, expertBlobW, cbExpertBlobW);
442 Stream_SealLength(s);
444 error = remdesk_virtual_channel_write(remdesk, s);
447 WLog_ERR(TAG,
"remdesk_virtual_channel_write failed with error %" PRIu32
"!", error);
460static UINT remdesk_send_ctl_expert_on_vista_pdu(
remdeskPlugin* remdesk)
464 WINPR_ASSERT(remdesk);
466 UINT error = remdesk_generate_expert_blob(remdesk);
469 WLog_ERR(TAG,
"remdesk_generate_expert_blob failed with error %" PRIu32
"!", error);
472 if (remdesk->EncryptedPassStubSize > UINT32_MAX)
473 return ERROR_INTERNAL_ERROR;
475 pdu.EncryptedPasswordLength = (UINT32)remdesk->EncryptedPassStubSize;
476 pdu.EncryptedPassword = remdesk->EncryptedPassStub;
477 error = remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_EXPERT_ON_VISTA,
478 pdu.EncryptedPasswordLength);
482 wStream* s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength);
486 WLog_ERR(TAG,
"Stream_New failed!");
487 return CHANNEL_RC_NO_MEMORY;
490 error = remdesk_write_ctl_header(s, &(pdu.ctlHeader));
493 Stream_Free(s, TRUE);
496 Stream_Write(s, pdu.EncryptedPassword, pdu.EncryptedPasswordLength);
497 Stream_SealLength(s);
498 return remdesk_virtual_channel_write(remdesk, s);
508 UINT error = CHANNEL_RC_OK;
512 WINPR_ASSERT(remdesk);
514 WINPR_ASSERT(header);
516 if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
517 return ERROR_INVALID_DATA;
519 Stream_Read_UINT32(s, msgType);
525 case REMDESK_CTL_REMOTE_CONTROL_DESKTOP:
528 case REMDESK_CTL_RESULT:
529 if ((error = remdesk_recv_ctl_result_pdu(remdesk, s, header, &result)))
530 WLog_ERR(TAG,
"remdesk_recv_ctl_result_pdu failed with error %" PRIu32
"", error);
534 case REMDESK_CTL_AUTHENTICATE:
537 case REMDESK_CTL_SERVER_ANNOUNCE:
538 if ((error = remdesk_recv_ctl_server_announce_pdu(remdesk, s, header)))
539 WLog_ERR(TAG,
"remdesk_recv_ctl_server_announce_pdu failed with error %" PRIu32
"",
544 case REMDESK_CTL_DISCONNECT:
547 case REMDESK_CTL_VERSIONINFO:
548 if ((error = remdesk_recv_ctl_version_info_pdu(remdesk, s, header)))
550 WLog_ERR(TAG,
"remdesk_recv_ctl_version_info_pdu failed with error %" PRIu32
"",
555 if (remdesk->Version == 1)
557 if ((error = remdesk_send_ctl_version_info_pdu(remdesk)))
559 WLog_ERR(TAG,
"remdesk_send_ctl_version_info_pdu failed with error %" PRIu32
"",
564 if ((error = remdesk_send_ctl_authenticate_pdu(remdesk)))
566 WLog_ERR(TAG,
"remdesk_send_ctl_authenticate_pdu failed with error %" PRIu32
"",
571 if ((error = remdesk_send_ctl_remote_control_desktop_pdu(remdesk)))
575 "remdesk_send_ctl_remote_control_desktop_pdu failed with error %" PRIu32
"",
580 else if (remdesk->Version == 2)
582 if ((error = remdesk_send_ctl_expert_on_vista_pdu(remdesk)))
585 "remdesk_send_ctl_expert_on_vista_pdu failed with error %" PRIu32
"",
590 if ((error = remdesk_send_ctl_verify_password_pdu(remdesk)))
593 "remdesk_send_ctl_verify_password_pdu failed with error %" PRIu32
"",
601 case REMDESK_CTL_ISCONNECTED:
604 case REMDESK_CTL_VERIFY_PASSWORD:
607 case REMDESK_CTL_EXPERT_ON_VISTA:
610 case REMDESK_CTL_RANOVICE_NAME:
613 case REMDESK_CTL_RAEXPERT_NAME:
616 case REMDESK_CTL_TOKEN:
620 WLog_ERR(TAG,
"unknown msgType: %" PRIu32
"", msgType);
621 error = ERROR_INVALID_DATA;
638 WINPR_ASSERT(remdesk);
641 if ((status = remdesk_read_channel_header(s, &header)))
643 WLog_ERR(TAG,
"remdesk_read_channel_header failed with error %" PRIu32
"", status);
647 if (strcmp(header.ChannelName,
"RC_CTL") == 0)
649 status = remdesk_recv_ctl_pdu(remdesk, s, &header);
651 else if (strcmp(header.ChannelName,
"70") == 0)
654 else if (strcmp(header.ChannelName,
"71") == 0)
657 else if (strcmp(header.ChannelName,
".") == 0)
660 else if (strcmp(header.ChannelName,
"1000.") == 0)
663 else if (strcmp(header.ChannelName,
"RA_FX") == 0)
673static void remdesk_process_connect(WINPR_ATTR_UNUSED
remdeskPlugin* remdesk)
675 WINPR_ASSERT(remdesk);
676 WLog_ERR(
"TODO",
"TODO: implement");
684static UINT remdesk_virtual_channel_event_data_received(
remdeskPlugin* remdesk,
const void* pData,
685 UINT32 dataLength, UINT32 totalLength,
690 WINPR_ASSERT(remdesk);
692 if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME))
694 return CHANNEL_RC_OK;
697 if (dataFlags & CHANNEL_FLAG_FIRST)
699 if (remdesk->data_in)
700 Stream_Free(remdesk->data_in, TRUE);
702 remdesk->data_in = Stream_New(NULL, totalLength);
704 if (!remdesk->data_in)
706 WLog_ERR(TAG,
"Stream_New failed!");
707 return CHANNEL_RC_NO_MEMORY;
711 data_in = remdesk->data_in;
713 if (!Stream_EnsureRemainingCapacity(data_in, dataLength))
715 WLog_ERR(TAG,
"Stream_EnsureRemainingCapacity failed!");
716 return CHANNEL_RC_NO_MEMORY;
719 Stream_Write(data_in, pData, dataLength);
721 if (dataFlags & CHANNEL_FLAG_LAST)
723 if (Stream_Capacity(data_in) != Stream_GetPosition(data_in))
725 WLog_ERR(TAG,
"read error");
726 return ERROR_INTERNAL_ERROR;
729 remdesk->data_in = NULL;
730 Stream_SealLength(data_in);
731 Stream_SetPosition(data_in, 0);
733 if (!MessageQueue_Post(remdesk->queue, NULL, 0, (
void*)data_in, NULL))
735 WLog_ERR(TAG,
"MessageQueue_Post failed!");
736 return ERROR_INTERNAL_ERROR;
740 return CHANNEL_RC_OK;
743static VOID VCAPITYPE remdesk_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle,
744 UINT event, LPVOID pData,
745 UINT32 dataLength, UINT32 totalLength,
748 UINT error = CHANNEL_RC_OK;
753 case CHANNEL_EVENT_INITIALIZED:
756 case CHANNEL_EVENT_DATA_RECEIVED:
757 if (!remdesk || (remdesk->OpenHandle != openHandle))
759 WLog_ERR(TAG,
"error no match");
762 if ((error = remdesk_virtual_channel_event_data_received(remdesk, pData, dataLength,
763 totalLength, dataFlags)))
765 "remdesk_virtual_channel_event_data_received failed with error %" PRIu32
771 case CHANNEL_EVENT_WRITE_CANCELLED:
772 case CHANNEL_EVENT_WRITE_COMPLETE:
775 Stream_Free(s, TRUE);
779 case CHANNEL_EVENT_USER:
783 WLog_ERR(TAG,
"unhandled event %" PRIu32
"!", event);
784 error = ERROR_INTERNAL_ERROR;
788 if (error && remdesk && remdesk->rdpcontext)
789 setChannelError(remdesk->rdpcontext, error,
790 "remdesk_virtual_channel_open_event_ex reported an error");
793static DWORD WINAPI remdesk_virtual_channel_client_thread(LPVOID arg)
796 wMessage message = { 0 };
798 UINT error = CHANNEL_RC_OK;
800 WINPR_ASSERT(remdesk);
802 remdesk_process_connect(remdesk);
806 if (!MessageQueue_Wait(remdesk->queue))
808 WLog_ERR(TAG,
"MessageQueue_Wait failed!");
809 error = ERROR_INTERNAL_ERROR;
813 if (!MessageQueue_Peek(remdesk->queue, &message, TRUE))
815 WLog_ERR(TAG,
"MessageQueue_Peek failed!");
816 error = ERROR_INTERNAL_ERROR;
820 if (message.id == WMQ_QUIT)
825 data = (
wStream*)message.wParam;
827 if ((error = remdesk_process_receive(remdesk, data)))
829 WLog_ERR(TAG,
"remdesk_process_receive failed with error %" PRIu32
"!", error);
830 Stream_Free(data, TRUE);
834 Stream_Free(data, TRUE);
838 if (error && remdesk->rdpcontext)
839 setChannelError(remdesk->rdpcontext, error,
840 "remdesk_virtual_channel_client_thread reported an error");
851static UINT remdesk_virtual_channel_event_connected(
remdeskPlugin* remdesk,
852 WINPR_ATTR_UNUSED LPVOID pData,
853 WINPR_ATTR_UNUSED UINT32 dataLength)
857 WINPR_ASSERT(remdesk);
859 remdesk->queue = MessageQueue_New(NULL);
863 WLog_ERR(TAG,
"MessageQueue_New failed!");
864 error = CHANNEL_RC_NO_MEMORY;
869 CreateThread(NULL, 0, remdesk_virtual_channel_client_thread, (
void*)remdesk, 0, NULL);
871 if (!remdesk->thread)
873 WLog_ERR(TAG,
"CreateThread failed");
874 error = ERROR_INTERNAL_ERROR;
878 return remdesk->channelEntryPoints.pVirtualChannelOpenEx(
879 remdesk->InitHandle, &remdesk->OpenHandle, remdesk->channelDef.name,
880 remdesk_virtual_channel_open_event_ex);
882 MessageQueue_Free(remdesk->queue);
883 remdesk->queue = NULL;
892static UINT remdesk_virtual_channel_event_disconnected(
remdeskPlugin* remdesk)
894 UINT rc = CHANNEL_RC_OK;
896 WINPR_ASSERT(remdesk);
898 if (remdesk->queue && remdesk->thread)
900 if (MessageQueue_PostQuit(remdesk->queue, 0) &&
901 (WaitForSingleObject(remdesk->thread, INFINITE) == WAIT_FAILED))
904 WLog_ERR(TAG,
"WaitForSingleObject failed with error %" PRIu32
"", rc);
909 if (remdesk->OpenHandle != 0)
911 WINPR_ASSERT(remdesk->channelEntryPoints.pVirtualChannelCloseEx);
912 rc = remdesk->channelEntryPoints.pVirtualChannelCloseEx(remdesk->InitHandle,
913 remdesk->OpenHandle);
915 if (CHANNEL_RC_OK != rc)
917 WLog_ERR(TAG,
"pVirtualChannelCloseEx failed with %s [%08" PRIX32
"]",
918 WTSErrorToString(rc), rc);
921 remdesk->OpenHandle = 0;
923 MessageQueue_Free(remdesk->queue);
924 (void)CloseHandle(remdesk->thread);
925 Stream_Free(remdesk->data_in, TRUE);
926 remdesk->data_in = NULL;
927 remdesk->queue = NULL;
928 remdesk->thread = NULL;
932static void remdesk_virtual_channel_event_terminated(
remdeskPlugin* remdesk)
934 WINPR_ASSERT(remdesk);
936 remdesk->InitHandle = 0;
937 free(remdesk->context);
941static VOID VCAPITYPE remdesk_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle,
942 UINT event, LPVOID pData,
945 UINT error = CHANNEL_RC_OK;
948 if (!remdesk || (remdesk->InitHandle != pInitHandle))
950 WLog_ERR(TAG,
"error no match");
956 case CHANNEL_EVENT_CONNECTED:
957 if ((error = remdesk_virtual_channel_event_connected(remdesk, pData, dataLength)))
959 "remdesk_virtual_channel_event_connected failed with error %" PRIu32
"",
964 case CHANNEL_EVENT_DISCONNECTED:
965 if ((error = remdesk_virtual_channel_event_disconnected(remdesk)))
967 "remdesk_virtual_channel_event_disconnected failed with error %" PRIu32
"",
972 case CHANNEL_EVENT_TERMINATED:
973 remdesk_virtual_channel_event_terminated(remdesk);
976 case CHANNEL_EVENT_ATTACHED:
977 case CHANNEL_EVENT_DETACHED:
982 if (error && remdesk->rdpcontext)
983 setChannelError(remdesk->rdpcontext, error,
984 "remdesk_virtual_channel_init_event reported an error");
988#define VirtualChannelEntryEx remdesk_VirtualChannelEntryEx
990FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints,
1007 WLog_ERR(TAG,
"calloc failed!");
1011 remdesk->channelDef.options = CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP |
1012 CHANNEL_OPTION_COMPRESS_RDP | CHANNEL_OPTION_SHOW_PROTOCOL;
1013 (void)sprintf_s(remdesk->channelDef.name, ARRAYSIZE(remdesk->channelDef.name),
1014 REMDESK_SVC_CHANNEL_NAME);
1015 remdesk->Version = 2;
1019 (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER))
1025 WLog_ERR(TAG,
"calloc failed!");
1029 context->handle = (
void*)remdesk;
1030 remdesk->context = context;
1031 remdesk->rdpcontext = pEntryPointsEx->context;
1034 CopyMemory(&(remdesk->channelEntryPoints), pEntryPoints,
1036 remdesk->InitHandle = pInitHandle;
1037 rc = remdesk->channelEntryPoints.pVirtualChannelInitEx(
1038 remdesk, context, pInitHandle, &remdesk->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000,
1039 remdesk_virtual_channel_init_event_ex);
1041 if (CHANNEL_RC_OK != rc)
1043 WLog_ERR(TAG,
"pVirtualChannelInitEx failed with %s [%08" PRIX32
"]", WTSErrorToString(rc),
1048 remdesk->channelEntryPoints.pInterface = context;
FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.