1 /**
2  * FreeRDP: A Remote Desktop Protocol Implementation
3  * FreeRDP Proxy Server
4  *
5  * Copyright 2019 Mati Shabtay <matishabtay@gmail.com>
6  * Copyright 2019 Kobi Mizrachi <kmizrachi18@gmail.com>
7  * Copyright 2019 Idan Freiberg <speidy@gmail.com>
8  *
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
12  *
13  *     http://www.apache.org/licenses/LICENSE-2.0
14  *
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.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include <freerdp/freerdp.h>
27 #include <freerdp/gdi/gdi.h>
28 #include <freerdp/client/cmdline.h>
29 
30 #include "pf_channels.h"
31 #include "pf_gdi.h"
32 #include "pf_graphics.h"
33 #include "pf_client.h"
34 #include "pf_context.h"
35 #include "pf_update.h"
36 #include "pf_log.h"
37 #include "pf_modules.h"
38 #include "pf_input.h"
39 #include "pf_capture.h"
40 
41 #define TAG PROXY_TAG("client")
42 
43 static pReceiveChannelData client_receive_channel_data_original = NULL;
44 
proxy_server_reactivate(rdpContext * ps,const rdpContext * pc)45 static BOOL proxy_server_reactivate(rdpContext* ps, const rdpContext* pc)
46 {
47 	if (!pf_context_copy_settings(ps->settings, pc->settings))
48 		return FALSE;
49 
50 	/*
51 	 * DesktopResize causes internal function rdp_server_reactivate to be called,
52 	 * which causes the reactivation.
53 	 */
54 	if (!ps->update->DesktopResize(ps))
55 		return FALSE;
56 
57 	return TRUE;
58 }
59 
pf_client_on_error_info(void * ctx,ErrorInfoEventArgs * e)60 static void pf_client_on_error_info(void* ctx, ErrorInfoEventArgs* e)
61 {
62 	pClientContext* pc = (pClientContext*)ctx;
63 	pServerContext* ps = pc->pdata->ps;
64 
65 	if (e->code == ERRINFO_NONE)
66 		return;
67 
68 	LOG_WARN(TAG, pc, "received ErrorInfo PDU. code=0x%08" PRIu32 ", message: %s", e->code,
69 	         freerdp_get_error_info_string(e->code));
70 
71 	/* forward error back to client */
72 	freerdp_set_error_info(ps->context.rdp, e->code);
73 	freerdp_send_error_info(ps->context.rdp);
74 }
75 
pf_client_on_activated(void * ctx,ActivatedEventArgs * e)76 static void pf_client_on_activated(void* ctx, ActivatedEventArgs* e)
77 {
78 	pClientContext* pc = (pClientContext*)ctx;
79 	pServerContext* ps = pc->pdata->ps;
80 	freerdp_peer* peer = ps->context.peer;
81 
82 	LOG_INFO(TAG, pc, "client activated, registering server input callbacks");
83 
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);
87 }
88 
pf_client_load_rdpsnd(pClientContext * pc)89 static BOOL pf_client_load_rdpsnd(pClientContext* pc)
90 {
91 	rdpContext* context = (rdpContext*)pc;
92 	pServerContext* ps = pc->pdata->ps;
93 	proxyConfig* config = pc->pdata->config;
94 
95 	/*
96 	 * if AudioOutput is enabled in proxy and client connected with rdpsnd, use proxy as rdpsnd
97 	 * backend. Otherwise, use sys:fake.
98 	 */
99 	if (!freerdp_static_channel_collection_find(context->settings, "rdpsnd"))
100 	{
101 		char* params[2];
102 		params[0] = "rdpsnd";
103 
104 		if (config->AudioOutput && WTSVirtualChannelManagerIsChannelJoined(ps->vcm, "rdpsnd"))
105 			params[1] = "sys:proxy";
106 		else
107 			params[1] = "sys:fake";
108 
109 		if (!freerdp_client_add_static_channel(context->settings, 2, (char**)params))
110 			return FALSE;
111 	}
112 
113 	return TRUE;
114 }
115 
pf_client_passthrough_channels_init(pClientContext * pc)116 static BOOL pf_client_passthrough_channels_init(pClientContext* pc)
117 {
118 	pServerContext* ps = pc->pdata->ps;
119 	rdpSettings* settings = pc->context.settings;
120 	proxyConfig* config = pc->pdata->config;
121 	size_t i;
122 
123 	if (settings->ChannelCount + config->PassthroughCount >= settings->ChannelDefArraySize)
124 	{
125 		LOG_ERR(TAG, pc, "too many channels");
126 		return FALSE;
127 	}
128 
129 	for (i = 0; i < config->PassthroughCount; i++)
130 	{
131 		const char* channel_name = config->Passthrough[i];
132 		CHANNEL_DEF channel = { 0 };
133 
134 		/* only connect connect this channel if already joined in peer connection */
135 		if (!WTSVirtualChannelManagerIsChannelJoined(ps->vcm, channel_name))
136 		{
137 			LOG_INFO(TAG, ps, "client did not connected with channel %s, skipping passthrough",
138 			         channel_name);
139 
140 			continue;
141 		}
142 
143 		channel.options = CHANNEL_OPTION_INITIALIZED; /* TODO: Export to config. */
144 		strncpy(channel.name, channel_name, CHANNEL_NAME_LEN);
145 
146 		settings->ChannelDefArray[settings->ChannelCount++] = channel;
147 	}
148 
149 	return TRUE;
150 }
151 
pf_client_use_peer_load_balance_info(pClientContext * pc)152 static BOOL pf_client_use_peer_load_balance_info(pClientContext* pc)
153 {
154 	pServerContext* ps = pc->pdata->ps;
155 	rdpSettings* settings = pc->context.settings;
156 	DWORD lb_info_len;
157 	const char* lb_info = freerdp_nego_get_routing_token(&ps->context, &lb_info_len);
158 	if (!lb_info)
159 		return TRUE;
160 
161 	free(settings->LoadBalanceInfo);
162 
163 	settings->LoadBalanceInfoLength = lb_info_len;
164 	settings->LoadBalanceInfo = malloc(settings->LoadBalanceInfoLength);
165 
166 	if (!settings->LoadBalanceInfo)
167 		return FALSE;
168 
169 	CopyMemory(settings->LoadBalanceInfo, lb_info, settings->LoadBalanceInfoLength);
170 	return TRUE;
171 }
172 
pf_client_pre_connect(freerdp * instance)173 static BOOL pf_client_pre_connect(freerdp* instance)
174 {
175 	pClientContext* pc = (pClientContext*)instance->context;
176 	pServerContext* ps = pc->pdata->ps;
177 	proxyConfig* config = ps->pdata->config;
178 	rdpSettings* settings = instance->settings;
179 
180 	/*
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.
184 	 *
185 	 * Also, OrderSupport need to be zeroed, because it is currently not supported.
186 	 */
187 	settings->GlyphSupportLevel = GLYPH_SUPPORT_NONE;
188 	ZeroMemory(instance->settings->OrderSupport, 32);
189 
190 	settings->SupportDynamicChannels = TRUE;
191 
192 	/* Multimon */
193 	settings->UseMultimon = TRUE;
194 
195 	/* Sound */
196 	settings->AudioPlayback = FALSE;
197 	settings->DeviceRedirection = TRUE;
198 
199 	/* Display control */
200 	settings->SupportDisplayControl = config->DisplayControl;
201 	settings->DynamicResolutionUpdate = config->DisplayControl;
202 
203 	settings->AutoReconnectionEnabled = TRUE;
204 
205 	/**
206 	 * Register the channel listeners.
207 	 * They are required to set up / tear down channels if they are loaded.
208 	 */
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);
215 	/**
216 	 * Load all required plugins / channels / libraries specified by current
217 	 * settings.
218 	 */
219 	LOG_INFO(TAG, pc, "Loading addins");
220 
221 	if (!config->UseLoadBalanceInfo)
222 	{
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.
225 		 */
226 
227 		if (!pf_client_use_peer_load_balance_info(pc))
228 			return FALSE;
229 	}
230 
231 	if (!pf_client_passthrough_channels_init(pc))
232 		return FALSE;
233 
234 	if (!pf_client_load_rdpsnd(pc))
235 	{
236 		LOG_ERR(TAG, pc, "Failed to load rdpsnd client");
237 		return FALSE;
238 	}
239 
240 	if (!freerdp_client_load_addins(instance->context->channels, instance->settings))
241 	{
242 		LOG_ERR(TAG, pc, "Failed to load addins");
243 		return FALSE;
244 	}
245 
246 	return TRUE;
247 }
248 
pf_client_receive_channel_data_hook(freerdp * instance,UINT16 channelId,const BYTE * data,size_t size,UINT32 flags,size_t totalSize)249 static BOOL pf_client_receive_channel_data_hook(freerdp* instance, UINT16 channelId,
250                                                 const BYTE* data, size_t size, UINT32 flags,
251                                                 size_t totalSize)
252 {
253 	pClientContext* pc = (pClientContext*)instance->context;
254 	pServerContext* ps = pc->pdata->ps;
255 	proxyData* pdata = ps->pdata;
256 	proxyConfig* config = pdata->config;
257 	size_t i;
258 
259 	const char* channel_name = freerdp_channels_get_name_by_id(instance, channelId);
260 
261 	for (i = 0; i < config->PassthroughCount; i++)
262 	{
263 		if (strncmp(channel_name, config->Passthrough[i], CHANNEL_NAME_LEN) == 0)
264 		{
265 			proxyChannelDataEventInfo ev;
266 			UINT64 server_channel_id;
267 
268 			ev.channel_id = channelId;
269 			ev.channel_name = channel_name;
270 			ev.data = data;
271 			ev.data_len = size;
272 
273 			if (!pf_modules_run_filter(FILTER_TYPE_CLIENT_PASSTHROUGH_CHANNEL_DATA, pdata, &ev))
274 				return FALSE;
275 
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,
278 			                                         data, size);
279 		}
280 	}
281 
282 	return client_receive_channel_data_original(instance, channelId, data, size, flags, totalSize);
283 }
284 
pf_client_on_server_heartbeat(freerdp * instance,BYTE period,BYTE count1,BYTE count2)285 static BOOL pf_client_on_server_heartbeat(freerdp* instance, BYTE period, BYTE count1, BYTE count2)
286 {
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);
290 }
291 
292 /**
293  * Called after a RDP connection was successfully established.
294  * Settings might have changed during negotiation of client / server feature
295  * support.
296  *
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
300  */
pf_client_post_connect(freerdp * instance)301 static BOOL pf_client_post_connect(freerdp* instance)
302 {
303 	rdpContext* context;
304 	rdpSettings* settings;
305 	rdpUpdate* update;
306 	rdpContext* ps;
307 	pClientContext* pc;
308 	proxyConfig* config;
309 
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;
316 
317 	if (config->SessionCapture)
318 	{
319 		if (!pf_capture_create_session_directory(pc))
320 		{
321 			LOG_ERR(TAG, pc, "pf_capture_create_session_directory failed!");
322 			return FALSE;
323 		}
324 
325 		LOG_ERR(TAG, pc, "frames dir created: %s", pc->frames_dir);
326 	}
327 
328 	if (!gdi_init(instance, PIXEL_FORMAT_BGRA32))
329 		return FALSE;
330 
331 	if (!pf_register_pointer(context->graphics))
332 		return FALSE;
333 
334 	if (!settings->SoftwareGdi)
335 	{
336 		if (!pf_register_graphics(context->graphics))
337 		{
338 			LOG_ERR(TAG, pc, "failed to register graphics");
339 			return FALSE;
340 		}
341 
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);
348 	}
349 
350 	pf_client_register_update_callbacks(update);
351 
352 	/* virtual channels receive data hook */
353 	client_receive_channel_data_original = instance->ReceiveChannelData;
354 	instance->ReceiveChannelData = pf_client_receive_channel_data_hook;
355 
356 	/* populate channel name -> channel ids map */
357 	{
358 		size_t i;
359 		for (i = 0; i < config->PassthroughCount; i++)
360 		{
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);
364 		}
365 	}
366 
367 	instance->heartbeat->ServerHeartbeat = pf_client_on_server_heartbeat;
368 
369 	/*
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.
373 	 */
374 	return proxy_server_reactivate(ps, context);
375 }
376 
377 /* This function is called whether a session ends by failure or success.
378  * Clean up everything allocated by pre_connect and post_connect.
379  */
pf_client_post_disconnect(freerdp * instance)380 static void pf_client_post_disconnect(freerdp* instance)
381 {
382 	pClientContext* context;
383 	proxyData* pdata;
384 
385 	if (!instance)
386 		return;
387 
388 	if (!instance->context)
389 		return;
390 
391 	context = (pClientContext*)instance->context;
392 	pdata = context->pdata;
393 
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);
399 	gdi_free(instance);
400 
401 	/* Only close the connection if NLA fallback process is done */
402 	if (!context->allow_next_conn_failure)
403 		proxy_data_abort_connect(pdata);
404 }
405 
406 /*
407  * pf_client_should_retry_without_nla:
408  *
409  * returns TRUE if in case of connection failure, the client should try again without NLA.
410  * Otherwise, returns FALSE.
411  */
pf_client_should_retry_without_nla(pClientContext * pc)412 static BOOL pf_client_should_retry_without_nla(pClientContext* pc)
413 {
414 	rdpSettings* settings = pc->context.settings;
415 	proxyConfig* config = pc->pdata->config;
416 
417 	if (!config->ClientAllowFallbackToTls || !settings->NlaSecurity)
418 		return FALSE;
419 
420 	return config->ClientTlsSecurity || config->ClientRdpSecurity;
421 }
422 
pf_client_set_security_settings(pClientContext * pc)423 static void pf_client_set_security_settings(pClientContext* pc)
424 {
425 	rdpSettings* settings = pc->context.settings;
426 	proxyConfig* config = pc->pdata->config;
427 
428 	settings->RdpSecurity = config->ClientRdpSecurity;
429 	settings->TlsSecurity = config->ClientTlsSecurity;
430 	settings->NlaSecurity = FALSE;
431 
432 	if (!config->ClientNlaSecurity)
433 		return;
434 
435 	if (!settings->Username || !settings->Password)
436 		return;
437 
438 	settings->NlaSecurity = TRUE;
439 }
440 
pf_client_connect_without_nla(pClientContext * pc)441 static BOOL pf_client_connect_without_nla(pClientContext* pc)
442 {
443 	freerdp* instance = pc->context.instance;
444 	rdpSettings* settings = pc->context.settings;
445 
446 	/* disable NLA */
447 	settings->NlaSecurity = FALSE;
448 
449 	/* do not allow next connection failure */
450 	pc->allow_next_conn_failure = FALSE;
451 	return freerdp_connect(instance);
452 }
453 
pf_client_connect(freerdp * instance)454 static BOOL pf_client_connect(freerdp* instance)
455 {
456 	pClientContext* pc = (pClientContext*)instance->context;
457 	rdpSettings* settings = instance->settings;
458 	BOOL rc = FALSE;
459 	BOOL retry = FALSE;
460 
461 	LOG_INFO(TAG, pc, "connecting using client info: Username: %s, Domain: %s", settings->Username,
462 	         settings->Domain);
463 
464 	pf_client_set_security_settings(pc);
465 	if (pf_client_should_retry_without_nla(pc))
466 		retry = pc->allow_next_conn_failure = TRUE;
467 
468 	if (!freerdp_connect(instance))
469 	{
470 		if (!retry)
471 			goto out;
472 
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);
475 
476 		if (!pf_client_connect_without_nla(pc))
477 		{
478 			LOG_ERR(TAG, pc, "pf_client_connect_without_nla failed!");
479 			goto out;
480 		}
481 	}
482 
483 	rc = TRUE;
484 out:
485 	pc->allow_next_conn_failure = FALSE;
486 	return rc;
487 }
488 
489 /**
490  * RDP main loop.
491  * Connects RDP, loops while running and handles event and dispatch, cleans up
492  * after the connection ends.
493  */
pf_client_thread_proc(LPVOID arg)494 static DWORD WINAPI pf_client_thread_proc(LPVOID arg)
495 {
496 	freerdp* instance = (freerdp*)arg;
497 	pClientContext* pc = (pClientContext*)instance->context;
498 	proxyData* pdata = pc->pdata;
499 	DWORD nCount;
500 	DWORD status;
501 	HANDLE handles[65];
502 
503 	/*
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.
509 	 */
510 	handles[64] = pdata->abort_event;
511 
512 	if (!pf_modules_run_hook(HOOK_TYPE_CLIENT_PRE_CONNECT, pdata))
513 	{
514 		proxy_data_abort_connect(pdata);
515 		return FALSE;
516 	}
517 
518 	if (!pf_client_connect(instance))
519 	{
520 		proxy_data_abort_connect(pdata);
521 		return FALSE;
522 	}
523 
524 	while (!freerdp_shall_disconnect(instance))
525 	{
526 		nCount = freerdp_get_event_handles(instance->context, &handles[0], 64);
527 
528 		if (nCount == 0)
529 		{
530 			LOG_ERR(TAG, pc, "freerdp_get_event_handles failed!");
531 			break;
532 		}
533 
534 		status = WaitForMultipleObjects(nCount, handles, FALSE, INFINITE);
535 
536 		if (status == WAIT_FAILED)
537 		{
538 			WLog_ERR(TAG, "%s: WaitForMultipleObjects failed with %" PRIu32 "", __FUNCTION__,
539 			         status);
540 			break;
541 		}
542 
543 		if (freerdp_shall_disconnect(instance))
544 			break;
545 
546 		if (proxy_data_shall_disconnect(pdata))
547 			break;
548 
549 		if (!freerdp_check_event_handles(instance->context))
550 		{
551 			if (freerdp_get_last_error(instance->context) == FREERDP_ERROR_SUCCESS)
552 				WLog_ERR(TAG, "Failed to check FreeRDP event handles");
553 
554 			break;
555 		}
556 	}
557 
558 	freerdp_disconnect(instance);
559 	return 0;
560 }
561 
pf_logon_error_info(freerdp * instance,UINT32 data,UINT32 type)562 static int pf_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
563 {
564 	const char* str_data = freerdp_get_logon_error_info_data(data);
565 	const char* str_type = freerdp_get_logon_error_info_type(type);
566 
567 	if (!instance || !instance->context)
568 		return -1;
569 
570 	WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type);
571 	return 1;
572 }
573 
574 /**
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.
587  *
588  * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
589  */
pf_client_verify_certificate_ex(freerdp * instance,const char * host,UINT16 port,const char * common_name,const char * subject,const char * issuer,const char * fingerprint,DWORD flags)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,
593                                              DWORD flags)
594 {
595 	/* TODO: Add trust level to proxy configurable settings */
596 	return 1;
597 }
598 
599 /**
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.
615  *
616  * @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
617  */
pf_client_verify_changed_certificate_ex(freerdp * instance,const char * host,UINT16 port,const char * common_name,const char * subject,const char * issuer,const char * fingerprint,const char * old_subject,const char * old_issuer,const char * old_fingerprint,DWORD flags)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)
622 {
623 	/* TODO: Add trust level to proxy configurable settings */
624 	return 1;
625 }
626 
pf_client_context_free(freerdp * instance,rdpContext * context)627 static void pf_client_context_free(freerdp* instance, rdpContext* context)
628 {
629 	pClientContext* pc = (pClientContext*)context;
630 
631 	if (!pc)
632 		return;
633 
634 	free(pc->frames_dir);
635 	pc->frames_dir = NULL;
636 
637 	HashTable_Free(pc->vc_ids);
638 }
639 
pf_client_client_new(freerdp * instance,rdpContext * context)640 static BOOL pf_client_client_new(freerdp* instance, rdpContext* context)
641 {
642 	if (!instance || !context)
643 		return FALSE;
644 
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;
652 
653 	return TRUE;
654 }
655 
pf_client_client_stop(rdpContext * context)656 static int pf_client_client_stop(rdpContext* context)
657 {
658 	pClientContext* pc = (pClientContext*)context;
659 	proxyData* pdata = pc->pdata;
660 
661 	LOG_DBG(TAG, pc, "aborting client connection");
662 	proxy_data_abort_connect(pdata);
663 	freerdp_abort_connect(context->instance);
664 
665 	if (pdata->client_thread)
666 	{
667 		/*
668 		 * Wait for client thread to finish. No need to call CloseHandle() here, as
669 		 * it is the responsibility of `proxy_data_free`.
670 		 */
671 		LOG_DBG(TAG, pc, "waiting for client thread to finish");
672 		WaitForSingleObject(pdata->client_thread, INFINITE);
673 		LOG_DBG(TAG, pc, "thread finished");
674 	}
675 
676 	return 0;
677 }
678 
RdpClientEntry(RDP_CLIENT_ENTRY_POINTS * pEntryPoints)679 int RdpClientEntry(RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
680 {
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;
688 	return 0;
689 }
690 
691 /**
692  * Starts running a client connection towards target server.
693  */
pf_client_start(LPVOID arg)694 DWORD WINAPI pf_client_start(LPVOID arg)
695 {
696 	rdpContext* context = (rdpContext*)arg;
697 
698 	if (freerdp_client_start(context) != 0)
699 		return 1;
700 
701 	return pf_client_thread_proc(context->instance);
702 }
703