2 * FreeRDP: A Remote Desktop Protocol Implementation
5 * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
6 * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
7 * Copyright 2019 Idan Freiberg <speidy@gmail.com>
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
26 #include <freerdp/freerdp.h>
27 #include <freerdp/gdi/gdi.h>
28 #include <freerdp/client/cmdline.h>
30 #include "pf_channels.h"
32 #include "pf_graphics.h"
33 #include "pf_client.h"
34 #include "pf_context.h"
35 #include "pf_update.h"
37 #include "pf_modules.h"
39 #include "pf_capture.h"
41 #define TAG PROXY_TAG("client")
43 static pReceiveChannelData client_receive_channel_data_original = NULL;
45 static BOOL proxy_server_reactivate(rdpContext* ps, const rdpContext* pc)
47 if (!pf_context_copy_settings(ps->settings, pc->settings))
51 * DesktopResize causes internal function rdp_server_reactivate to be called,
52 * which causes the reactivation.
54 if (!ps->update->DesktopResize(ps))
60 static void pf_client_on_error_info(void* ctx, ErrorInfoEventArgs* e)
62 pClientContext* pc = (pClientContext*)ctx;
63 pServerContext* ps = pc->pdata->ps;
65 if (e->code == ERRINFO_NONE)
68 LOG_WARN(TAG, pc, "received ErrorInfo PDU. code=0x%08" PRIu32 ", message: %s", e->code,
69 freerdp_get_error_info_string(e->code));
71 /* forward error back to client */
72 freerdp_set_error_info(ps->context.rdp, e->code);
73 freerdp_send_error_info(ps->context.rdp);
76 static void pf_client_on_activated(void* ctx, ActivatedEventArgs* e)
78 pClientContext* pc = (pClientContext*)ctx;
79 pServerContext* ps = pc->pdata->ps;
80 freerdp_peer* peer = ps->context.peer;
82 LOG_INFO(TAG, pc, "client activated, registering server input callbacks");
84 /* Register server input/update callbacks only after proxy client is fully activated */
85 pf_server_register_input_callbacks(peer->input);
86 pf_server_register_update_callbacks(peer->update);
89 static BOOL pf_client_load_rdpsnd(pClientContext* pc)
91 rdpContext* context = (rdpContext*)pc;
92 pServerContext* ps = pc->pdata->ps;
93 proxyConfig* config = pc->pdata->config;
96 * if AudioOutput is enabled in proxy and client connected with rdpsnd, use proxy as rdpsnd
97 * backend. Otherwise, use sys:fake.
99 if (!freerdp_static_channel_collection_find(context->settings, "rdpsnd"))
102 params[0] = "rdpsnd";
104 if (config->AudioOutput && WTSVirtualChannelManagerIsChannelJoined(ps->vcm, "rdpsnd"))
105 params[1] = "sys:proxy";
107 params[1] = "sys:fake";
109 if (!freerdp_client_add_static_channel(context->settings, 2, (char**)params))
116 static BOOL pf_client_passthrough_channels_init(pClientContext* pc)
118 pServerContext* ps = pc->pdata->ps;
119 rdpSettings* settings = pc->context.settings;
120 proxyConfig* config = pc->pdata->config;
123 if (settings->ChannelCount + config->PassthroughCount >= settings->ChannelDefArraySize)
125 LOG_ERR(TAG, pc, "too many channels");
129 for (i = 0; i < config->PassthroughCount; i++)
131 const char* channel_name = config->Passthrough[i];
132 CHANNEL_DEF channel = { 0 };
134 /* only connect connect this channel if already joined in peer connection */
135 if (!WTSVirtualChannelManagerIsChannelJoined(ps->vcm, channel_name))
137 LOG_INFO(TAG, ps, "client did not connected with channel %s, skipping passthrough",
143 channel.options = CHANNEL_OPTION_INITIALIZED; /* TODO: Export to config. */
144 strncpy(channel.name, channel_name, CHANNEL_NAME_LEN);
146 settings->ChannelDefArray[settings->ChannelCount++] = channel;
152 static BOOL pf_client_use_peer_load_balance_info(pClientContext* pc)
154 pServerContext* ps = pc->pdata->ps;
155 rdpSettings* settings = pc->context.settings;
157 const char* lb_info = freerdp_nego_get_routing_token(&ps->context, &lb_info_len);
161 free(settings->LoadBalanceInfo);
163 settings->LoadBalanceInfoLength = lb_info_len;
164 settings->LoadBalanceInfo = malloc(settings->LoadBalanceInfoLength);
166 if (!settings->LoadBalanceInfo)
169 CopyMemory(settings->LoadBalanceInfo, lb_info, settings->LoadBalanceInfoLength);
173 static BOOL pf_client_pre_connect(freerdp* instance)
175 pClientContext* pc = (pClientContext*)instance->context;
176 pServerContext* ps = pc->pdata->ps;
177 proxyConfig* config = ps->pdata->config;
178 rdpSettings* settings = instance->settings;
181 * as the client's settings are copied from the server's, GlyphSupportLevel might not be
182 * GLYPH_SUPPORT_NONE. the proxy currently do not support GDI & GLYPH_SUPPORT_CACHE, so
183 * GlyphCacheSupport must be explicitly set to GLYPH_SUPPORT_NONE.
185 * Also, OrderSupport need to be zeroed, because it is currently not supported.
187 settings->GlyphSupportLevel = GLYPH_SUPPORT_NONE;
188 ZeroMemory(instance->settings->OrderSupport, 32);
190 settings->SupportDynamicChannels = TRUE;
193 settings->UseMultimon = TRUE;
196 settings->AudioPlayback = FALSE;
197 settings->DeviceRedirection = TRUE;
199 /* Display control */
200 settings->SupportDisplayControl = config->DisplayControl;
201 settings->DynamicResolutionUpdate = config->DisplayControl;
203 settings->AutoReconnectionEnabled = TRUE;
206 * Register the channel listeners.
207 * They are required to set up / tear down channels if they are loaded.
209 PubSub_SubscribeChannelConnected(instance->context->pubSub,
210 pf_channels_on_client_channel_connect);
211 PubSub_SubscribeChannelDisconnected(instance->context->pubSub,
212 pf_channels_on_client_channel_disconnect);
213 PubSub_SubscribeErrorInfo(instance->context->pubSub, pf_client_on_error_info);
214 PubSub_SubscribeActivated(instance->context->pubSub, pf_client_on_activated);
216 * Load all required plugins / channels / libraries specified by current
219 LOG_INFO(TAG, pc, "Loading addins");
221 if (!config->UseLoadBalanceInfo)
223 /* if target is static (i.e fetched from config). make sure to use peer's load-balance info,
224 * in order to support broker redirection.
227 if (!pf_client_use_peer_load_balance_info(pc))
231 if (!pf_client_passthrough_channels_init(pc))
234 if (!pf_client_load_rdpsnd(pc))
236 LOG_ERR(TAG, pc, "Failed to load rdpsnd client");
240 if (!freerdp_client_load_addins(instance->context->channels, instance->settings))
242 LOG_ERR(TAG, pc, "Failed to load addins");
249 static BOOL pf_client_receive_channel_data_hook(freerdp* instance, UINT16 channelId,
250 const BYTE* data, size_t size, UINT32 flags,
253 pClientContext* pc = (pClientContext*)instance->context;
254 pServerContext* ps = pc->pdata->ps;
255 proxyData* pdata = ps->pdata;
256 proxyConfig* config = pdata->config;
259 const char* channel_name = freerdp_channels_get_name_by_id(instance, channelId);
261 for (i = 0; i < config->PassthroughCount; i++)
263 if (strncmp(channel_name, config->Passthrough[i], CHANNEL_NAME_LEN) == 0)
265 proxyChannelDataEventInfo ev;
266 UINT64 server_channel_id;
268 ev.channel_id = channelId;
269 ev.channel_name = channel_name;
273 if (!pf_modules_run_filter(FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA, pdata, &ev))
276 server_channel_id = (UINT64)HashTable_GetItemValue(ps->vc_ids, (void*)channel_name);
277 return ps->context.peer->SendChannelData(ps->context.peer, (UINT16)server_channel_id,
282 return client_receive_channel_data_original(instance, channelId, data, size, flags, totalSize);
285 static BOOL pf_client_on_server_heartbeat(freerdp* instance, BYTE period, BYTE count1, BYTE count2)
287 pClientContext* pc = (pClientContext*)instance->context;
288 pServerContext* ps = pc->pdata->ps;
289 return freerdp_heartbeat_send_heartbeat_pdu(ps->context.peer, period, count1, count2);
293 * Called after a RDP connection was successfully established.
294 * Settings might have changed during negotiation of client / server feature
297 * Set up local framebuffers and painting callbacks.
298 * If required, register pointer callbacks to change the local mouse cursor
299 * when hovering over the RDP window
301 static BOOL pf_client_post_connect(freerdp* instance)
304 rdpSettings* settings;
310 context = instance->context;
311 settings = instance->settings;
312 update = instance->update;
313 pc = (pClientContext*)context;
314 ps = (rdpContext*)pc->pdata->ps;
315 config = pc->pdata->config;
317 if (config->SessionCapture)
319 if (!pf_capture_create_session_directory(pc))
321 LOG_ERR(TAG, pc, "pf_capture_create_session_directory failed!");
325 LOG_ERR(TAG, pc, "frames dir created: %s", pc->frames_dir);
328 if (!gdi_init(instance, PIXEL_FORMAT_BGRA32))
331 if (!pf_register_pointer(context->graphics))
334 if (!settings->SoftwareGdi)
336 if (!pf_register_graphics(context->graphics))
338 LOG_ERR(TAG, pc, "failed to register graphics");
342 pf_gdi_register_update_callbacks(update);
343 brush_cache_register_callbacks(update);
344 glyph_cache_register_callbacks(update);
345 bitmap_cache_register_callbacks(update);
346 offscreen_cache_register_callbacks(update);
347 palette_cache_register_callbacks(update);
350 pf_client_register_update_callbacks(update);
352 /* virtual channels receive data hook */
353 client_receive_channel_data_original = instance->ReceiveChannelData;
354 instance->ReceiveChannelData = pf_client_receive_channel_data_hook;
356 /* populate channel name -> channel ids map */
359 for (i = 0; i < config->PassthroughCount; i++)
361 char* channel_name = config->Passthrough[i];
362 UINT64 channel_id = (UINT64)freerdp_channels_get_id_by_name(instance, channel_name);
363 HashTable_Add(pc->vc_ids, (void*)channel_name, (void*)channel_id);
367 instance->heartbeat->ServerHeartbeat = pf_client_on_server_heartbeat;
370 * after the connection fully established and settings were negotiated with target server,
371 * send a reactivation sequence to the client with the negotiated settings. This way,
372 * settings are synchorinized between proxy's peer and and remote target.
374 return proxy_server_reactivate(ps, context);
377 /* This function is called whether a session ends by failure or success.
378 * Clean up everything allocated by pre_connect and post_connect.
380 static void pf_client_post_disconnect(freerdp* instance)
382 pClientContext* context;
388 if (!instance->context)
391 context = (pClientContext*)instance->context;
392 pdata = context->pdata;
394 PubSub_UnsubscribeChannelConnected(instance->context->pubSub,
395 pf_channels_on_client_channel_connect);
396 PubSub_UnsubscribeChannelDisconnected(instance->context->pubSub,
397 pf_channels_on_client_channel_disconnect);
398 PubSub_UnsubscribeErrorInfo(instance->context->pubSub, pf_client_on_error_info);
401 /* Only close the connection if NLA fallback process is done */
402 if (!context->allow_next_conn_failure)
403 proxy_data_abort_connect(pdata);
407 * pf_client_should_retry_without_nla:
409 * returns TRUE if in case of connection failure, the client should try again without NLA.
410 * Otherwise, returns FALSE.
412 static BOOL pf_client_should_retry_without_nla(pClientContext* pc)
414 rdpSettings* settings = pc->context.settings;
415 proxyConfig* config = pc->pdata->config;
417 if (!config->ClientAllowFallbackToTls || !settings->NlaSecurity)
420 return config->ClientTlsSecurity || config->ClientRdpSecurity;
423 static void pf_client_set_security_settings(pClientContext* pc)
425 rdpSettings* settings = pc->context.settings;
426 proxyConfig* config = pc->pdata->config;
428 settings->RdpSecurity = config->ClientRdpSecurity;
429 settings->TlsSecurity = config->ClientTlsSecurity;
430 settings->NlaSecurity = FALSE;
432 if (!config->ClientNlaSecurity)
435 if (!settings->Username || !settings->Password)
438 settings->NlaSecurity = TRUE;
441 static BOOL pf_client_connect_without_nla(pClientContext* pc)
443 freerdp* instance = pc->context.instance;
444 rdpSettings* settings = pc->context.settings;
447 settings->NlaSecurity = FALSE;
449 /* do not allow next connection failure */
450 pc->allow_next_conn_failure = FALSE;
451 return freerdp_connect(instance);
454 static BOOL pf_client_connect(freerdp* instance)
456 pClientContext* pc = (pClientContext*)instance->context;
457 rdpSettings* settings = instance->settings;
461 LOG_INFO(TAG, pc, "connecting using client info: Username: %s, Domain: %s", settings->Username,
464 pf_client_set_security_settings(pc);
465 if (pf_client_should_retry_without_nla(pc))
466 retry = pc->allow_next_conn_failure = TRUE;
468 if (!freerdp_connect(instance))
473 LOG_ERR(TAG, pc, "failed to connect with NLA. retrying to connect without NLA");
474 pf_modules_run_hook(HOOK_TYPE_CLIENT_LOGIN_FAILURE, pc->pdata);
476 if (!pf_client_connect_without_nla(pc))
478 LOG_ERR(TAG, pc, "pf_client_connect_without_nla failed!");
485 pc->allow_next_conn_failure = FALSE;
491 * Connects RDP, loops while running and handles event and dispatch, cleans up
492 * after the connection ends.
494 static DWORD WINAPI pf_client_thread_proc(LPVOID arg)
496 freerdp* instance = (freerdp*)arg;
497 pClientContext* pc = (pClientContext*)instance->context;
498 proxyData* pdata = pc->pdata;
504 * during redirection, freerdp's abort event might be overriden (reset) by the library, after
505 * the server set it in order to shutdown the connection. it means that the server might signal
506 * the client to abort, but the library code will override the signal and the client will
507 * continue its work instead of exiting. That's why the client must wait on `pdata->abort_event`
508 * too, which will never be modified by the library.
510 handles[64] = pdata->abort_event;
512 if (!pf_modules_run_hook(HOOK_TYPE_CLIENT_PRE_CONNECT, pdata))
514 proxy_data_abort_connect(pdata);
518 if (!pf_client_connect(instance))
520 proxy_data_abort_connect(pdata);
524 while (!freerdp_shall_disconnect(instance))
526 nCount = freerdp_get_event_handles(instance->context, &handles[0], 64);
530 LOG_ERR(TAG, pc, "freerdp_get_event_handles failed!");
534 status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE);
536 if (status == WAIT_FAILED)
538 WLog_ERR(TAG, "%s: WaitForMultipleObjects failed with %" PRIu32 "", __FUNCTION__,
543 if (freerdp_shall_disconnect(instance))
546 if (proxy_data_shall_disconnect(pdata))
549 if (!freerdp_check_event_handles(instance->context))
551 if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS)
552 WLog_ERR(TAG, "Failed to check FreeRDP event handles");
558 freerdp_disconnect(instance);
562 static int pf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
564 const char* str_data = freerdp_get_logon_error_info_data(data);
565 const char* str_type = freerdp_get_logon_error_info_type(type);
567 if (!instance || !instance->context)
570 WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type);
575 * Callback set in the rdp_freerdp structure, and used to make a certificate validation
576 * when the connection requires it.
577 * This function will actually be called by tls_verify_certificate().
578 * @see rdp_client_connect() and tls_connect()
579 * @param instance pointer to the rdp_freerdp structure that contains the connection settings
580 * @param host The host currently connecting to
581 * @param port The port currently connecting to
582 * @param common_name The common name of the certificate, should match host or an alias of it
583 * @param subject The subject of the certificate
584 * @param issuer The certificate issuer name
585 * @param fingerprint The fingerprint of the certificate
586 * @param flags See VERIFY_CERT_FLAG_* for possible values.
588 * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
590 static DWORD pf_client_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
591 const char* common_name, const char* subject,
592 const char* issuer, const char* fingerprint,
595 /* TODO: Add trust level to proxy configurable settings */
600 * Callback set in the rdp_freerdp structure, and used to make a certificate validation
601 * when a stored certificate does not match the remote counterpart.
602 * This function will actually be called by tls_verify_certificate().
603 * @see rdp_client_connect() and tls_connect()
604 * @param instance pointer to the rdp_freerdp structure that contains the connection settings
605 * @param host The host currently connecting to
606 * @param port The port currently connecting to
607 * @param common_name The common name of the certificate, should match host or an alias of it
608 * @param subject The subject of the certificate
609 * @param issuer The certificate issuer name
610 * @param fingerprint The fingerprint of the certificate
611 * @param old_subject The subject of the previous certificate
612 * @param old_issuer The previous certificate issuer name
613 * @param old_fingerprint The fingerprint of the previous certificate
614 * @param flags See VERIFY_CERT_FLAG_* for possible values.
616 * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
618 static DWORD pf_client_verify_changed_certificate_ex(
619 freerdp* instance, const char* host, UINT16 port, const char* common_name, const char* subject,
620 const char* issuer, const char* fingerprint, const char* old_subject, const char* old_issuer,
621 const char* old_fingerprint, DWORD flags)
623 /* TODO: Add trust level to proxy configurable settings */
627 static void pf_client_context_free(freerdp* instance, rdpContext* context)
629 pClientContext* pc = (pClientContext*)context;
634 free(pc->frames_dir);
635 pc->frames_dir = NULL;
637 HashTable_Free(pc->vc_ids);
640 static BOOL pf_client_client_new(freerdp* instance, rdpContext* context)
642 if (!instance || !context)
645 instance->PreConnect = pf_client_pre_connect;
646 instance->PostConnect = pf_client_post_connect;
647 instance->PostDisconnect = pf_client_post_disconnect;
648 instance->VerifyCertificateEx = pf_client_verify_certificate_ex;
649 instance->VerifyChangedCertificateEx = pf_client_verify_changed_certificate_ex;
650 instance->LogonErrorInfo = pf_logon_error_info;
651 instance->ContextFree = pf_client_context_free;
656 static int pf_client_client_stop(rdpContext* context)
658 pClientContext* pc = (pClientContext*)context;
659 proxyData* pdata = pc->pdata;
661 LOG_DBG(TAG, pc, "aborting client connection");
662 proxy_data_abort_connect(pdata);
663 freerdp_abort_connect(context->instance);
665 if (pdata->client_thread)
668 * Wait for client thread to finish. No need to call CloseHandle() here, as
669 * it is the responsibility of `proxy_data_free`.
671 LOG_DBG(TAG, pc, "waiting for client thread to finish");
672 WaitForSingleObject(pdata->client_thread, INFINITE);
673 LOG_DBG(TAG, pc, "thread finished");
679 int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
681 ZeroMemory(pEntryPoints, sizeof(RDP_CLIENT_ENTRY_POINTS));
682 pEntryPoints->Version = RDP_CLIENT_INTERFACE_VERSION;
683 pEntryPoints->Size = sizeof(RDP_CLIENT_ENTRY_POINTS_V1);
684 pEntryPoints->ContextSize = sizeof(pClientContext);
685 /* Client init and finish */
686 pEntryPoints->ClientNew = pf_client_client_new;
687 pEntryPoints->ClientStop = pf_client_client_stop;
692 * Starts running a client connection towards target server.
694 DWORD WINAPI pf_client_start(LPVOID arg)
696 rdpContext* context = (rdpContext*)arg;
698 if (freerdp_client_start(context) != 0)
701 return pf_client_thread_proc(context->instance);