server: proxy: fix race issue
[platform/upstream/freerdp.git] / server / proxy / pf_client.c
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
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
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
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
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
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
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
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
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
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  */
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  */
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  */
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
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
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
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  */
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
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  */
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  */
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
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
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
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
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  */
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 }