22 #include <freerdp/config.h>
24 #include <winpr/crt.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);
71 static 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;
141 WINPR_ASSERT(remdesk);
143 WINPR_ASSERT(header);
145 return CHANNEL_RC_OK;
156 UINT32 versionMajor = 0;
157 UINT32 versionMinor = 0;
159 WINPR_ASSERT(remdesk);
161 WINPR_ASSERT(header);
163 if (!Stream_CheckAndLogRequiredLength(TAG, s, 8))
164 return ERROR_INVALID_DATA;
166 Stream_Read_UINT32(s, versionMajor);
167 Stream_Read_UINT32(s, versionMinor);
169 if ((versionMajor != 1) || (versionMinor > 2) || (versionMinor == 0))
171 WLog_ERR(TAG,
"Unsupported protocol version %" PRId32
".%" PRId32, versionMajor,
175 remdesk->Version = versionMinor;
176 return CHANNEL_RC_OK;
184 static UINT remdesk_send_ctl_version_info_pdu(
remdeskPlugin* remdesk)
188 WINPR_ASSERT(remdesk);
190 UINT error = remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_VERSIONINFO, 8);
194 pdu.versionMajor = 1;
195 pdu.versionMinor = 2;
196 wStream* s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength);
200 WLog_ERR(TAG,
"Stream_New failed!");
201 return CHANNEL_RC_NO_MEMORY;
204 error = remdesk_write_ctl_header(s, &(pdu.ctlHeader));
207 Stream_Free(s, TRUE);
210 Stream_Write_UINT32(s, pdu.versionMajor);
211 Stream_Write_UINT32(s, pdu.versionMinor);
212 Stream_SealLength(s);
214 if ((error = remdesk_virtual_channel_write(remdesk, s)))
215 WLog_ERR(TAG,
"remdesk_virtual_channel_write failed with error %" PRIu32
"!", error);
230 WINPR_ASSERT(remdesk);
232 WINPR_ASSERT(header);
233 WINPR_ASSERT(pResult);
235 if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
236 return ERROR_INVALID_DATA;
238 Stream_Read_UINT32(s, result);
243 case REMDESK_ERROR_HELPEESAIDNO:
244 WLog_DBG(TAG,
"remote assistance connection request was denied");
245 return ERROR_CONNECTION_REFUSED;
251 return CHANNEL_RC_OK;
259 static UINT remdesk_send_ctl_authenticate_pdu(
remdeskPlugin* remdesk)
261 UINT error = ERROR_INTERNAL_ERROR;
262 size_t cbExpertBlobW = 0;
263 WCHAR* expertBlobW = NULL;
264 size_t cbRaConnectionStringW = 0;
267 WINPR_ASSERT(remdesk);
269 if ((error = remdesk_generate_expert_blob(remdesk)))
271 WLog_ERR(TAG,
"remdesk_generate_expert_blob failed with error %" PRIu32
"", error);
275 const char* expertBlob = remdesk->ExpertBlob;
276 WINPR_ASSERT(remdesk->rdpcontext);
277 rdpSettings* settings = remdesk->rdpcontext->settings;
278 WINPR_ASSERT(settings);
280 const char* raConnectionString =
282 WCHAR* raConnectionStringW =
283 ConvertUtf8ToWCharAlloc(raConnectionString, &cbRaConnectionStringW);
285 if (!raConnectionStringW || (cbRaConnectionStringW > UINT32_MAX /
sizeof(WCHAR)))
288 cbRaConnectionStringW = cbRaConnectionStringW *
sizeof(WCHAR);
290 expertBlobW = ConvertUtf8ToWCharAlloc(expertBlob, &cbExpertBlobW);
295 cbExpertBlobW = cbExpertBlobW *
sizeof(WCHAR);
296 error = remdesk_prepare_ctl_header(&(ctlHeader), REMDESK_CTL_AUTHENTICATE,
297 cbRaConnectionStringW + cbExpertBlobW);
301 wStream* s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + ctlHeader.ch.DataLength);
305 WLog_ERR(TAG,
"Stream_New failed!");
306 error = CHANNEL_RC_NO_MEMORY;
310 error = remdesk_write_ctl_header(s, &ctlHeader);
313 Stream_Free(s, TRUE);
316 Stream_Write(s, raConnectionStringW, cbRaConnectionStringW);
317 Stream_Write(s, expertBlobW, cbExpertBlobW);
318 Stream_SealLength(s);
320 error = remdesk_virtual_channel_write(remdesk, s);
322 WLog_ERR(TAG,
"remdesk_virtual_channel_write failed with error %" PRIu32
"!", error);
325 free(raConnectionStringW);
336 static UINT remdesk_send_ctl_remote_control_desktop_pdu(
remdeskPlugin* remdesk)
341 WINPR_ASSERT(remdesk);
342 WINPR_ASSERT(remdesk->rdpcontext);
343 rdpSettings* settings = remdesk->rdpcontext->settings;
344 WINPR_ASSERT(settings);
346 const char* raConnectionString =
348 WCHAR* raConnectionStringW = ConvertUtf8ToWCharAlloc(raConnectionString, &length);
349 size_t cbRaConnectionStringW = length *
sizeof(WCHAR);
351 if (!raConnectionStringW)
352 return ERROR_INTERNAL_ERROR;
355 error = remdesk_prepare_ctl_header(&ctlHeader, REMDESK_CTL_REMOTE_CONTROL_DESKTOP,
356 cbRaConnectionStringW);
357 if (error != CHANNEL_RC_OK)
360 wStream* s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + ctlHeader.ch.DataLength);
364 WLog_ERR(TAG,
"Stream_New failed!");
365 error = CHANNEL_RC_NO_MEMORY;
369 error = remdesk_write_ctl_header(s, &ctlHeader);
372 Stream_Free(s, TRUE);
375 Stream_Write(s, raConnectionStringW, cbRaConnectionStringW);
376 Stream_SealLength(s);
378 if ((error = remdesk_virtual_channel_write(remdesk, s)))
379 WLog_ERR(TAG,
"remdesk_virtual_channel_write failed with error %" PRIu32
"!", error);
382 free(raConnectionStringW);
392 static UINT remdesk_send_ctl_verify_password_pdu(
remdeskPlugin* remdesk)
394 size_t cbExpertBlobW = 0;
397 WINPR_ASSERT(remdesk);
399 UINT error = remdesk_generate_expert_blob(remdesk);
402 WLog_ERR(TAG,
"remdesk_generate_expert_blob failed with error %" PRIu32
"!", error);
406 pdu.expertBlob = remdesk->ExpertBlob;
407 WCHAR* expertBlobW = ConvertUtf8ToWCharAlloc(pdu.expertBlob, &cbExpertBlobW);
412 cbExpertBlobW = cbExpertBlobW *
sizeof(WCHAR);
414 remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_VERIFY_PASSWORD, cbExpertBlobW);
418 wStream* s = Stream_New(NULL, 1ULL * REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength);
422 WLog_ERR(TAG,
"Stream_New failed!");
423 error = CHANNEL_RC_NO_MEMORY;
427 error = remdesk_write_ctl_header(s, &(pdu.ctlHeader));
430 Stream_Free(s, TRUE);
433 Stream_Write(s, expertBlobW, cbExpertBlobW);
434 Stream_SealLength(s);
436 error = remdesk_virtual_channel_write(remdesk, s);
438 WLog_ERR(TAG,
"remdesk_virtual_channel_write failed with error %" PRIu32
"!", error);
451 static UINT remdesk_send_ctl_expert_on_vista_pdu(
remdeskPlugin* remdesk)
455 WINPR_ASSERT(remdesk);
457 UINT error = remdesk_generate_expert_blob(remdesk);
460 WLog_ERR(TAG,
"remdesk_generate_expert_blob failed with error %" PRIu32
"!", error);
463 if (remdesk->EncryptedPassStubSize > UINT32_MAX)
464 return ERROR_INTERNAL_ERROR;
466 pdu.EncryptedPasswordLength = (UINT32)remdesk->EncryptedPassStubSize;
467 pdu.EncryptedPassword = remdesk->EncryptedPassStub;
468 error = remdesk_prepare_ctl_header(&(pdu.ctlHeader), REMDESK_CTL_EXPERT_ON_VISTA,
469 pdu.EncryptedPasswordLength);
473 wStream* s = Stream_New(NULL, REMDESK_CHANNEL_CTL_SIZE + pdu.ctlHeader.ch.DataLength);
477 WLog_ERR(TAG,
"Stream_New failed!");
478 return CHANNEL_RC_NO_MEMORY;
481 error = remdesk_write_ctl_header(s, &(pdu.ctlHeader));
484 Stream_Free(s, TRUE);
487 Stream_Write(s, pdu.EncryptedPassword, pdu.EncryptedPasswordLength);
488 Stream_SealLength(s);
489 return remdesk_virtual_channel_write(remdesk, s);
499 UINT error = CHANNEL_RC_OK;
503 WINPR_ASSERT(remdesk);
505 WINPR_ASSERT(header);
507 if (!Stream_CheckAndLogRequiredLength(TAG, s, 4))
508 return ERROR_INVALID_DATA;
510 Stream_Read_UINT32(s, msgType);
516 case REMDESK_CTL_REMOTE_CONTROL_DESKTOP:
519 case REMDESK_CTL_RESULT:
520 if ((error = remdesk_recv_ctl_result_pdu(remdesk, s, header, &result)))
521 WLog_ERR(TAG,
"remdesk_recv_ctl_result_pdu failed with error %" PRIu32
"", error);
525 case REMDESK_CTL_AUTHENTICATE:
528 case REMDESK_CTL_SERVER_ANNOUNCE:
529 if ((error = remdesk_recv_ctl_server_announce_pdu(remdesk, s, header)))
530 WLog_ERR(TAG,
"remdesk_recv_ctl_server_announce_pdu failed with error %" PRIu32
"",
535 case REMDESK_CTL_DISCONNECT:
538 case REMDESK_CTL_VERSIONINFO:
539 if ((error = remdesk_recv_ctl_version_info_pdu(remdesk, s, header)))
541 WLog_ERR(TAG,
"remdesk_recv_ctl_version_info_pdu failed with error %" PRIu32
"",
546 if (remdesk->Version == 1)
548 if ((error = remdesk_send_ctl_version_info_pdu(remdesk)))
550 WLog_ERR(TAG,
"remdesk_send_ctl_version_info_pdu failed with error %" PRIu32
"",
555 if ((error = remdesk_send_ctl_authenticate_pdu(remdesk)))
557 WLog_ERR(TAG,
"remdesk_send_ctl_authenticate_pdu failed with error %" PRIu32
"",
562 if ((error = remdesk_send_ctl_remote_control_desktop_pdu(remdesk)))
566 "remdesk_send_ctl_remote_control_desktop_pdu failed with error %" PRIu32
"",
571 else if (remdesk->Version == 2)
573 if ((error = remdesk_send_ctl_expert_on_vista_pdu(remdesk)))
576 "remdesk_send_ctl_expert_on_vista_pdu failed with error %" PRIu32
"",
581 if ((error = remdesk_send_ctl_verify_password_pdu(remdesk)))
584 "remdesk_send_ctl_verify_password_pdu failed with error %" PRIu32
"",
592 case REMDESK_CTL_ISCONNECTED:
595 case REMDESK_CTL_VERIFY_PASSWORD:
598 case REMDESK_CTL_EXPERT_ON_VISTA:
601 case REMDESK_CTL_RANOVICE_NAME:
604 case REMDESK_CTL_RAEXPERT_NAME:
607 case REMDESK_CTL_TOKEN:
611 WLog_ERR(TAG,
"unknown msgType: %" PRIu32
"", msgType);
612 error = ERROR_INVALID_DATA;
629 WINPR_ASSERT(remdesk);
633 WLog_DBG(TAG,
"RemdeskReceive: %"PRIuz
"", Stream_GetRemainingLength(s));
634 winpr_HexDump(Stream_ConstPointer(s), Stream_GetRemainingLength(s));
637 if ((status = remdesk_read_channel_header(s, &header)))
639 WLog_ERR(TAG,
"remdesk_read_channel_header failed with error %" PRIu32
"", status);
643 if (strcmp(header.ChannelName,
"RC_CTL") == 0)
645 status = remdesk_recv_ctl_pdu(remdesk, s, &header);
647 else if (strcmp(header.ChannelName,
"70") == 0)
650 else if (strcmp(header.ChannelName,
"71") == 0)
653 else if (strcmp(header.ChannelName,
".") == 0)
656 else if (strcmp(header.ChannelName,
"1000.") == 0)
659 else if (strcmp(header.ChannelName,
"RA_FX") == 0)
671 WINPR_ASSERT(remdesk);
679 static UINT remdesk_virtual_channel_event_data_received(
remdeskPlugin* remdesk,
const void* pData,
680 UINT32 dataLength, UINT32 totalLength,
685 WINPR_ASSERT(remdesk);
687 if ((dataFlags & CHANNEL_FLAG_SUSPEND) || (dataFlags & CHANNEL_FLAG_RESUME))
689 return CHANNEL_RC_OK;
692 if (dataFlags & CHANNEL_FLAG_FIRST)
694 if (remdesk->data_in)
695 Stream_Free(remdesk->data_in, TRUE);
697 remdesk->data_in = Stream_New(NULL, totalLength);
699 if (!remdesk->data_in)
701 WLog_ERR(TAG,
"Stream_New failed!");
702 return CHANNEL_RC_NO_MEMORY;
706 data_in = remdesk->data_in;
708 if (!Stream_EnsureRemainingCapacity(data_in, dataLength))
710 WLog_ERR(TAG,
"Stream_EnsureRemainingCapacity failed!");
711 return CHANNEL_RC_NO_MEMORY;
714 Stream_Write(data_in, pData, dataLength);
716 if (dataFlags & CHANNEL_FLAG_LAST)
718 if (Stream_Capacity(data_in) != Stream_GetPosition(data_in))
720 WLog_ERR(TAG,
"read error");
721 return ERROR_INTERNAL_ERROR;
724 remdesk->data_in = NULL;
725 Stream_SealLength(data_in);
726 Stream_SetPosition(data_in, 0);
728 if (!MessageQueue_Post(remdesk->queue, NULL, 0, (
void*)data_in, NULL))
730 WLog_ERR(TAG,
"MessageQueue_Post failed!");
731 return ERROR_INTERNAL_ERROR;
735 return CHANNEL_RC_OK;
738 static VOID VCAPITYPE remdesk_virtual_channel_open_event_ex(LPVOID lpUserParam, DWORD openHandle,
739 UINT event, LPVOID pData,
740 UINT32 dataLength, UINT32 totalLength,
743 UINT error = CHANNEL_RC_OK;
748 case CHANNEL_EVENT_INITIALIZED:
751 case CHANNEL_EVENT_DATA_RECEIVED:
752 if (!remdesk || (remdesk->OpenHandle != openHandle))
754 WLog_ERR(TAG,
"error no match");
757 if ((error = remdesk_virtual_channel_event_data_received(remdesk, pData, dataLength,
758 totalLength, dataFlags)))
760 "remdesk_virtual_channel_event_data_received failed with error %" PRIu32
766 case CHANNEL_EVENT_WRITE_CANCELLED:
767 case CHANNEL_EVENT_WRITE_COMPLETE:
770 Stream_Free(s, TRUE);
774 case CHANNEL_EVENT_USER:
778 WLog_ERR(TAG,
"unhandled event %" PRIu32
"!", event);
779 error = ERROR_INTERNAL_ERROR;
783 if (error && remdesk && remdesk->rdpcontext)
784 setChannelError(remdesk->rdpcontext, error,
785 "remdesk_virtual_channel_open_event_ex reported an error");
788 static DWORD WINAPI remdesk_virtual_channel_client_thread(LPVOID arg)
791 wMessage message = { 0 };
793 UINT error = CHANNEL_RC_OK;
795 WINPR_ASSERT(remdesk);
797 remdesk_process_connect(remdesk);
801 if (!MessageQueue_Wait(remdesk->queue))
803 WLog_ERR(TAG,
"MessageQueue_Wait failed!");
804 error = ERROR_INTERNAL_ERROR;
808 if (!MessageQueue_Peek(remdesk->queue, &message, TRUE))
810 WLog_ERR(TAG,
"MessageQueue_Peek failed!");
811 error = ERROR_INTERNAL_ERROR;
815 if (message.id == WMQ_QUIT)
820 data = (
wStream*)message.wParam;
822 if ((error = remdesk_process_receive(remdesk, data)))
824 WLog_ERR(TAG,
"remdesk_process_receive failed with error %" PRIu32
"!", error);
825 Stream_Free(data, TRUE);
829 Stream_Free(data, TRUE);
833 if (error && remdesk->rdpcontext)
834 setChannelError(remdesk->rdpcontext, error,
835 "remdesk_virtual_channel_client_thread reported an error");
846 static UINT remdesk_virtual_channel_event_connected(
remdeskPlugin* remdesk, LPVOID pData,
851 WINPR_ASSERT(remdesk);
853 remdesk->queue = MessageQueue_New(NULL);
857 WLog_ERR(TAG,
"MessageQueue_New failed!");
858 error = CHANNEL_RC_NO_MEMORY;
863 CreateThread(NULL, 0, remdesk_virtual_channel_client_thread, (
void*)remdesk, 0, NULL);
865 if (!remdesk->thread)
867 WLog_ERR(TAG,
"CreateThread failed");
868 error = ERROR_INTERNAL_ERROR;
872 return remdesk->channelEntryPoints.pVirtualChannelOpenEx(
873 remdesk->InitHandle, &remdesk->OpenHandle, remdesk->channelDef.name,
874 remdesk_virtual_channel_open_event_ex);
876 MessageQueue_Free(remdesk->queue);
877 remdesk->queue = NULL;
886 static UINT remdesk_virtual_channel_event_disconnected(
remdeskPlugin* remdesk)
888 UINT rc = CHANNEL_RC_OK;
890 WINPR_ASSERT(remdesk);
892 if (remdesk->queue && remdesk->thread)
894 if (MessageQueue_PostQuit(remdesk->queue, 0) &&
895 (WaitForSingleObject(remdesk->thread, INFINITE) == WAIT_FAILED))
898 WLog_ERR(TAG,
"WaitForSingleObject failed with error %" PRIu32
"", rc);
903 if (remdesk->OpenHandle != 0)
905 WINPR_ASSERT(remdesk->channelEntryPoints.pVirtualChannelCloseEx);
906 rc = remdesk->channelEntryPoints.pVirtualChannelCloseEx(remdesk->InitHandle,
907 remdesk->OpenHandle);
909 if (CHANNEL_RC_OK != rc)
911 WLog_ERR(TAG,
"pVirtualChannelCloseEx failed with %s [%08" PRIX32
"]",
912 WTSErrorToString(rc), rc);
915 remdesk->OpenHandle = 0;
917 MessageQueue_Free(remdesk->queue);
918 (void)CloseHandle(remdesk->thread);
919 Stream_Free(remdesk->data_in, TRUE);
920 remdesk->data_in = NULL;
921 remdesk->queue = NULL;
922 remdesk->thread = NULL;
926 static void remdesk_virtual_channel_event_terminated(
remdeskPlugin* remdesk)
928 WINPR_ASSERT(remdesk);
930 remdesk->InitHandle = 0;
931 free(remdesk->context);
935 static VOID VCAPITYPE remdesk_virtual_channel_init_event_ex(LPVOID lpUserParam, LPVOID pInitHandle,
936 UINT event, LPVOID pData,
939 UINT error = CHANNEL_RC_OK;
942 if (!remdesk || (remdesk->InitHandle != pInitHandle))
944 WLog_ERR(TAG,
"error no match");
950 case CHANNEL_EVENT_CONNECTED:
951 if ((error = remdesk_virtual_channel_event_connected(remdesk, pData, dataLength)))
953 "remdesk_virtual_channel_event_connected failed with error %" PRIu32
"",
958 case CHANNEL_EVENT_DISCONNECTED:
959 if ((error = remdesk_virtual_channel_event_disconnected(remdesk)))
961 "remdesk_virtual_channel_event_disconnected failed with error %" PRIu32
"",
966 case CHANNEL_EVENT_TERMINATED:
967 remdesk_virtual_channel_event_terminated(remdesk);
970 case CHANNEL_EVENT_ATTACHED:
971 case CHANNEL_EVENT_DETACHED:
976 if (error && remdesk->rdpcontext)
977 setChannelError(remdesk->rdpcontext, error,
978 "remdesk_virtual_channel_init_event reported an error");
982 #define VirtualChannelEntryEx remdesk_VirtualChannelEntryEx
984 FREERDP_ENTRY_POINT(BOOL VCAPITYPE VirtualChannelEntryEx(PCHANNEL_ENTRY_POINTS pEntryPoints,
1001 WLog_ERR(TAG,
"calloc failed!");
1005 remdesk->channelDef.options = CHANNEL_OPTION_INITIALIZED | CHANNEL_OPTION_ENCRYPT_RDP |
1006 CHANNEL_OPTION_COMPRESS_RDP | CHANNEL_OPTION_SHOW_PROTOCOL;
1007 (void)sprintf_s(remdesk->channelDef.name, ARRAYSIZE(remdesk->channelDef.name),
1008 REMDESK_SVC_CHANNEL_NAME);
1009 remdesk->Version = 2;
1013 (pEntryPointsEx->MagicNumber == FREERDP_CHANNEL_MAGIC_NUMBER))
1019 WLog_ERR(TAG,
"calloc failed!");
1023 context->handle = (
void*)remdesk;
1024 remdesk->context = context;
1025 remdesk->rdpcontext = pEntryPointsEx->context;
1028 CopyMemory(&(remdesk->channelEntryPoints), pEntryPoints,
1030 remdesk->InitHandle = pInitHandle;
1031 rc = remdesk->channelEntryPoints.pVirtualChannelInitEx(
1032 remdesk, context, pInitHandle, &remdesk->channelDef, 1, VIRTUAL_CHANNEL_VERSION_WIN2000,
1033 remdesk_virtual_channel_init_event_ex);
1035 if (CHANNEL_RC_OK != rc)
1037 WLog_ERR(TAG,
"pVirtualChannelInitEx failed with %s [%08" PRIX32
"]", WTSErrorToString(rc),
1042 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.