Coverage Report

Created: 2026-10-28 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/client/common/client.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * FreeRDP Client Common
4
 *
5
 * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
6
 * Copyright 2025 Siemens
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
#if !defined(_WIN32)
22
#include <fcntl.h>
23
#include <sys/types.h>
24
#include <sys/stat.h>
25
#endif
26
27
#include <winpr/cast.h>
28
#include <winpr/i18n.h>
29
#include <winpr/pipe.h>
30
31
#include <freerdp/config.h>
32
#include <freerdp/utils/signal.h>
33
34
#include <string.h>
35
#include <errno.h>
36
#include <math.h>
37
#include <limits.h>
38
#include <float.h>
39
40
#include <freerdp/client.h>
41
42
#include <freerdp/freerdp.h>
43
#include <freerdp/addin.h>
44
#include <freerdp/assistance.h>
45
#include <freerdp/client/file.h>
46
#include <freerdp/utils/passphrase.h>
47
#include <freerdp/client/cmdline.h>
48
#include <freerdp/client/channels.h>
49
#include <freerdp/event.h>
50
#include <freerdp/utils/smartcardlogon.h>
51
#include <freerdp/session.h>
52
53
#if defined(CHANNEL_AINPUT_CLIENT)
54
#include <freerdp/client/ainput.h>
55
#include <freerdp/channels/ainput.h>
56
#endif
57
58
#if defined(CHANNEL_VIDEO_CLIENT)
59
#include <freerdp/client/video.h>
60
#include <freerdp/channels/video.h>
61
#endif
62
63
#if defined(CHANNEL_RDPGFX_CLIENT)
64
#include <freerdp/client/rdpgfx.h>
65
#include <freerdp/channels/rdpgfx.h>
66
#include <freerdp/gdi/gfx.h>
67
#endif
68
69
#if defined(CHANNEL_GEOMETRY_CLIENT)
70
#include <freerdp/client/geometry.h>
71
#include <freerdp/channels/geometry.h>
72
#endif
73
74
#if defined(CHANNEL_GEOMETRY_CLIENT) || defined(CHANNEL_VIDEO_CLIENT)
75
#include <freerdp/gdi/video.h>
76
#endif
77
78
#include <freerdp/channels/rdpewa.h>
79
80
#ifdef WITH_AAD
81
#include <freerdp/utils/http.h>
82
#include <freerdp/utils/aad.h>
83
#include <freerdp/client/aad_helper.h>
84
#endif
85
86
#ifdef WITH_SSO_MIB
87
#include "sso_mib_tokens.h"
88
#endif
89
90
#include "oauth2.h"
91
92
#include <freerdp/log.h>
93
94
WINPR_ATTR_NODISCARD
95
static BOOL freerdp_client_common_pre_connect(rdpContext* context);
96
97
BOOL freerdp_client_enable_translation(const char* locale, const char* searchPath)
98
0
{
99
0
  return freerdp_enable_translation(locale, searchPath) &&
100
0
         winpr_i18n_bind_domain("freerdp-client", searchPath);
101
0
}
102
#define TAG CLIENT_TAG("common")
103
104
typedef struct
105
{
106
  freerdp* instance;
107
  window_events_fkt_t window_events;
108
  bool running;
109
} handle_window_events_t;
110
111
WINPR_ATTR_NODISCARD
112
static BOOL requireRdpClientContext(const rdpContext* context)
113
6.20k
{
114
6.20k
  if (!context)
115
0
  {
116
0
    WLog_ERR(TAG, "[APPLICATION BUG] Function must be called with a context != NULL!");
117
0
    return FALSE;
118
0
  }
119
120
6.20k
  freerdp* instance = context->instance;
121
6.20k
  if (!instance)
122
0
  {
123
0
    WLog_ERR(TAG,
124
0
             "[APPLICATION BUG] Function must be called with a context->instance != NULL!");
125
0
    return FALSE;
126
0
  }
127
128
6.20k
  RDP_CLIENT_ENTRY_POINTS* pEntryPoints = instance->pClientEntryPoints;
129
6.20k
  if (!pEntryPoints)
130
0
  {
131
0
    WLog_ERR(TAG, "[APPLICATION BUG] Function must be called with a "
132
0
                  "context->instance->pclientEntryPoints != NULL!");
133
0
    return FALSE;
134
0
  }
135
136
  /* This function must be called with a context containing rdpClientContext. So if the size is
137
   * not large enough that is a terminal failure. */
138
6.20k
  if (sizeof(rdpClientContext) > instance->ContextSize)
139
6.20k
  {
140
6.20k
    WLog_ERR(
141
6.20k
        TAG,
142
6.20k
        "[APPLICATION BUG] Function must be called with a context based on rdpClientContext!");
143
6.20k
    WLog_ERR(TAG,
144
6.20k
             "[APPLICATION BUG] sizeof(rdpClientContext)[%" PRIuz
145
6.20k
             "] > instance->ContextSize[%" PRIuz "])",
146
6.20k
             sizeof(rdpClientContext), instance->ContextSize);
147
6.20k
    return FALSE;
148
6.20k
  }
149
150
0
  return TRUE;
151
6.20k
}
152
153
static void set_default_callbacks(freerdp* instance)
154
2.06k
{
155
2.06k
  WINPR_ASSERT(instance);
156
2.06k
  instance->AuthenticateEx = client_cli_authenticate_ex;
157
2.06k
  instance->ChooseSmartcard = client_cli_choose_smartcard;
158
2.06k
  instance->VerifyCertificateEx = client_cli_verify_certificate_ex;
159
2.06k
  instance->VerifyChangedCertificateEx = client_cli_verify_changed_certificate_ex;
160
2.06k
  instance->PresentGatewayMessage = client_cli_present_gateway_message;
161
2.06k
  instance->LogonErrorInfo = client_cli_logon_error_info;
162
2.06k
  instance->GetAccessToken = client_cli_get_access_token;
163
2.06k
  instance->RetryDialog = client_common_retry_dialog;
164
165
2.06k
  WINPR_ASSERT(instance->context);
166
2.06k
  WINPR_ASSERT(instance->context->update);
167
2.06k
  instance->context->update->SaveSessionInfo = client_common_save_session_info;
168
2.06k
}
169
170
static void client_cli_user_notification(void* context, const UserNotificationEventArgs* e)
171
0
{
172
0
  WINPR_UNUSED(context);
173
0
  WINPR_ASSERT(e);
174
0
  if (strcmp(e->e.Sender, RDPEWA_CHANNEL_NAME) != 0)
175
0
    return;
176
177
0
  if (!e->message || e->message[0] == '\0')
178
0
    return;
179
0
  (void)fprintf(stderr, "[%s] Touch the security key\n", e->e.Sender);
180
0
  (void)fflush(stderr);
181
0
}
182
183
WINPR_ATTR_NODISCARD
184
static BOOL freerdp_client_common_new(freerdp* instance, rdpContext* context)
185
2.06k
{
186
2.06k
  WINPR_ASSERT(instance);
187
2.06k
  WINPR_ASSERT(context);
188
189
2.06k
  instance->LoadChannels = freerdp_client_load_channels;
190
2.06k
  set_default_callbacks(instance);
191
192
2.06k
  RDP_CLIENT_ENTRY_POINTS* pEntryPoints = instance->pClientEntryPoints;
193
2.06k
  WINPR_ASSERT(pEntryPoints);
194
195
2.06k
  if (requireRdpClientContext(context))
196
0
  {
197
0
    rdpClientContext* cctx = (rdpClientContext*)context;
198
0
    cctx->oauth2 = freerdp_oauth2_new();
199
0
    if (!cctx->oauth2)
200
0
      return FALSE;
201
0
  }
202
203
2.06k
  const BOOL rc = IFCALLRESULT(TRUE, pEntryPoints->ClientNew, instance, context);
204
2.06k
  if (!rc)
205
0
    return FALSE;
206
2.06k
  return freerdp_client_common_pre_connect(context);
207
2.06k
}
208
209
static void freerdp_client_common_free(freerdp* instance, rdpContext* context)
210
2.06k
{
211
2.06k
  WINPR_ASSERT(instance);
212
2.06k
  WINPR_ASSERT(context);
213
214
2.06k
  RDP_CLIENT_ENTRY_POINTS* pEntryPoints = instance->pClientEntryPoints;
215
2.06k
  WINPR_ASSERT(pEntryPoints);
216
2.06k
  IFCALL(pEntryPoints->ClientFree, instance, context);
217
218
2.06k
  if (requireRdpClientContext(context))
219
0
  {
220
0
    rdpClientContext* cctx = (rdpClientContext*)context;
221
0
    freerdp_oauth2_free(cctx->oauth2);
222
0
    cctx->oauth2 = nullptr;
223
0
  }
224
2.06k
}
225
226
/* Common API */
227
228
rdpContext* freerdp_client_context_new(const RDP_CLIENT_ENTRY_POINTS* pEntryPoints)
229
2.06k
{
230
2.06k
  freerdp* instance = nullptr;
231
2.06k
  rdpContext* context = nullptr;
232
233
2.06k
  if (!pEntryPoints)
234
0
    return nullptr;
235
236
2.06k
  if (!IFCALLRESULT(TRUE, pEntryPoints->GlobalInit))
237
0
    return nullptr;
238
239
2.06k
  instance = freerdp_new();
240
241
2.06k
  if (!instance)
242
0
    return nullptr;
243
244
2.06k
  instance->ContextSize = pEntryPoints->ContextSize;
245
2.06k
  instance->ContextNew = freerdp_client_common_new;
246
2.06k
  instance->ContextFree = freerdp_client_common_free;
247
2.06k
  instance->pClientEntryPoints = (RDP_CLIENT_ENTRY_POINTS*)malloc(pEntryPoints->Size);
248
249
2.06k
  if (!instance->pClientEntryPoints)
250
0
    goto out_fail;
251
252
2.06k
  CopyMemory(instance->pClientEntryPoints, pEntryPoints, pEntryPoints->Size);
253
254
2.06k
  if (!freerdp_context_new_ex(instance, pEntryPoints->settings))
255
0
    goto out_fail2;
256
257
2.06k
  context = instance->context;
258
2.06k
  context->instance = instance;
259
260
2.06k
#if defined(WITH_CLIENT_CHANNELS)
261
2.06k
  if (freerdp_register_addin_provider(freerdp_channels_load_static_addin_entry, 0) !=
262
2.06k
      CHANNEL_RC_OK)
263
0
    goto out_fail2;
264
2.06k
#endif
265
266
2.06k
  return context;
267
0
out_fail2:
268
0
  free(instance->pClientEntryPoints);
269
0
out_fail:
270
0
  freerdp_free(instance);
271
0
  return nullptr;
272
0
}
273
274
void freerdp_client_context_free(rdpContext* context)
275
2.06k
{
276
2.06k
  freerdp* instance = nullptr;
277
278
2.06k
  if (!context)
279
0
    return;
280
281
2.06k
  instance = context->instance;
282
283
2.06k
  if (instance)
284
2.06k
  {
285
2.06k
    RDP_CLIENT_ENTRY_POINTS* pEntryPoints = instance->pClientEntryPoints;
286
2.06k
    freerdp_context_free(instance);
287
288
2.06k
    if (pEntryPoints)
289
2.06k
      IFCALL(pEntryPoints->GlobalUninit);
290
291
2.06k
    free(instance->pClientEntryPoints);
292
2.06k
    freerdp_free(instance);
293
2.06k
  }
294
2.06k
}
295
296
int freerdp_client_start(rdpContext* context)
297
0
{
298
0
  RDP_CLIENT_ENTRY_POINTS* pEntryPoints = nullptr;
299
300
0
  if (!context || !context->instance || !context->instance->pClientEntryPoints)
301
0
    return ERROR_BAD_ARGUMENTS;
302
303
0
  if (freerdp_settings_get_bool(context->settings, FreeRDP_UseCommonStdioCallbacks))
304
0
  {
305
0
    set_default_callbacks(context->instance);
306
0
    if (context->pubSub)
307
0
    {
308
0
      const int rc =
309
0
          PubSub_SubscribeUserNotification(context->pubSub, client_cli_user_notification);
310
0
      if (rc < 0)
311
0
        return FALSE;
312
0
    }
313
0
  }
314
315
#ifdef WITH_SSO_MIB
316
  if (requireRdpClientContext(context))
317
  {
318
    rdpClientContext* client_context = (rdpClientContext*)context;
319
    client_context->mibClientWrapper = sso_mib_new(context);
320
    if (!client_context->mibClientWrapper)
321
      return ERROR_INTERNAL_ERROR;
322
  }
323
#endif
324
325
0
  pEntryPoints = context->instance->pClientEntryPoints;
326
0
  return IFCALLRESULT(CHANNEL_RC_OK, pEntryPoints->ClientStart, context);
327
0
}
328
329
int freerdp_client_stop(rdpContext* context)
330
0
{
331
0
  RDP_CLIENT_ENTRY_POINTS* pEntryPoints = nullptr;
332
333
0
  if (!context || !context->instance || !context->instance->pClientEntryPoints)
334
0
    return ERROR_BAD_ARGUMENTS;
335
336
0
  pEntryPoints = context->instance->pClientEntryPoints;
337
0
  const int rc = IFCALLRESULT(CHANNEL_RC_OK, pEntryPoints->ClientStop, context);
338
339
0
  if (freerdp_settings_get_bool(context->settings, FreeRDP_UseCommonStdioCallbacks))
340
0
    PubSub_UnsubscribeUserNotification(context->pubSub, client_cli_user_notification);
341
342
#ifdef WITH_SSO_MIB
343
  if (requireRdpClientContext(context))
344
  {
345
    rdpClientContext* client_context = (rdpClientContext*)context;
346
    sso_mib_free(client_context->mibClientWrapper);
347
    client_context->mibClientWrapper = nullptr;
348
  }
349
#endif // WITH_SSO_MIB
350
0
  return rc;
351
0
}
352
353
freerdp* freerdp_client_get_instance(rdpContext* context)
354
0
{
355
0
  if (!context || !context->instance)
356
0
    return nullptr;
357
358
0
  return context->instance;
359
0
}
360
361
HANDLE freerdp_client_get_thread(rdpContext* context)
362
0
{
363
0
  if (!context)
364
0
    return nullptr;
365
366
0
  WINPR_ASSERT(requireRdpClientContext(context));
367
0
  if (!requireRdpClientContext(context))
368
0
    return nullptr;
369
370
0
  return ((rdpClientContext*)context)->thread;
371
0
}
372
373
WINPR_ATTR_NODISCARD
374
static BOOL freerdp_client_settings_post_process(rdpSettings* settings)
375
0
{
376
  /* Moved GatewayUseSameCredentials logic outside of cmdline.c, so
377
   * that the rdp file also triggers this functionality */
378
0
  if (freerdp_settings_get_bool(settings, FreeRDP_GatewayEnabled))
379
0
  {
380
0
    if (freerdp_settings_get_bool(settings, FreeRDP_GatewayUseSameCredentials))
381
0
    {
382
0
      const char* Username = freerdp_settings_get_string(settings, FreeRDP_Username);
383
0
      const char* Domain = freerdp_settings_get_string(settings, FreeRDP_Domain);
384
0
      if (Username)
385
0
      {
386
0
        if (!freerdp_settings_set_string(settings, FreeRDP_GatewayUsername, Username))
387
0
          goto out_error;
388
0
      }
389
390
0
      if (Domain)
391
0
      {
392
0
        if (!freerdp_settings_set_string(settings, FreeRDP_GatewayDomain, Domain))
393
0
          goto out_error;
394
0
      }
395
396
0
      if (freerdp_settings_get_string(settings, FreeRDP_Password))
397
0
      {
398
0
        if (!freerdp_settings_set_string(
399
0
                settings, FreeRDP_GatewayPassword,
400
0
                freerdp_settings_get_string(settings, FreeRDP_Password)))
401
0
          goto out_error;
402
0
      }
403
0
    }
404
0
  }
405
406
  /* Moved logic for Multimon and Span monitors to force fullscreen, so
407
   * that the rdp file also triggers this functionality */
408
0
  if (freerdp_settings_get_bool(settings, FreeRDP_SpanMonitors))
409
0
  {
410
0
    if (!freerdp_settings_set_bool(settings, FreeRDP_UseMultimon, TRUE))
411
0
      goto out_error;
412
0
    if (!freerdp_settings_set_bool(settings, FreeRDP_Fullscreen, TRUE))
413
0
      goto out_error;
414
0
  }
415
0
  else if (freerdp_settings_get_bool(settings, FreeRDP_UseMultimon))
416
0
  {
417
0
    if (!freerdp_settings_set_bool(settings, FreeRDP_Fullscreen, TRUE))
418
0
      goto out_error;
419
0
  }
420
421
  /* deal with the smartcard / smartcard logon stuff */
422
0
  if (freerdp_settings_get_bool(settings, FreeRDP_SmartcardLogon))
423
0
  {
424
0
    if (!freerdp_settings_set_bool(settings, FreeRDP_RedirectSmartCards, TRUE))
425
0
      goto out_error;
426
0
    if (!freerdp_settings_set_bool(settings, FreeRDP_DeviceRedirection, TRUE))
427
0
      goto out_error;
428
0
    if (!freerdp_settings_set_bool(settings, FreeRDP_PasswordIsSmartcardPin, TRUE))
429
0
      goto out_error;
430
0
  }
431
432
0
  return TRUE;
433
0
out_error:
434
0
  return FALSE;
435
0
}
436
437
int freerdp_client_settings_parse_command_line(rdpSettings* settings, int argc, char** argv,
438
                                               BOOL allowUnknown)
439
440
0
{
441
0
  return freerdp_client_settings_parse_command_line_ex(settings, argc, argv, allowUnknown,
442
0
                                                       nullptr, 0, nullptr, nullptr);
443
0
}
444
445
int freerdp_client_settings_parse_command_line_ex(
446
    rdpSettings* settings, int argc, char** argv, BOOL allowUnknown, COMMAND_LINE_ARGUMENT_A* args,
447
    size_t count, freerdp_command_line_handle_option_t handle_option, void* handle_userdata)
448
0
{
449
0
  int status = 0;
450
451
0
  if (argc < 1)
452
0
    return 0;
453
454
0
  if (!argv)
455
0
    return -1;
456
457
0
  status = freerdp_client_settings_parse_command_line_arguments_ex(
458
0
      settings, argc, argv, allowUnknown, args, count, handle_option, handle_userdata);
459
460
0
  if (status < 0)
461
0
    return status;
462
463
  /* This function will call logic that is applicable to the settings
464
   * from command line parsing AND the rdp file parsing */
465
0
  if (!freerdp_client_settings_post_process(settings))
466
0
    status = -1;
467
468
0
  const char* name = argv[0];
469
0
  WLog_DBG(TAG, "This is [%s] %s %s", name, freerdp_get_version_string(),
470
0
           freerdp_get_build_config());
471
0
  return status;
472
0
}
473
474
int freerdp_client_settings_parse_connection_file(rdpSettings* settings, const char* filename)
475
0
{
476
0
  rdpFile* file = nullptr;
477
0
  int ret = -1;
478
0
  file = freerdp_client_rdp_file_new();
479
480
0
  if (!file)
481
0
    return -1;
482
483
0
  if (!freerdp_client_parse_rdp_file(file, filename))
484
0
    goto out;
485
486
0
  if (!freerdp_client_populate_settings_from_rdp_file(file, settings))
487
0
    goto out;
488
489
0
  ret = 0;
490
0
out:
491
0
  freerdp_client_rdp_file_free(file);
492
0
  return ret;
493
0
}
494
495
int freerdp_client_settings_parse_connection_file_buffer(rdpSettings* settings, const BYTE* buffer,
496
                                                         size_t size)
497
0
{
498
0
  rdpFile* file = nullptr;
499
0
  int status = -1;
500
0
  file = freerdp_client_rdp_file_new();
501
502
0
  if (!file)
503
0
    return -1;
504
505
0
  if (freerdp_client_parse_rdp_file_buffer(file, buffer, size) &&
506
0
      freerdp_client_populate_settings_from_rdp_file(file, settings))
507
0
  {
508
0
    status = 0;
509
0
  }
510
511
0
  freerdp_client_rdp_file_free(file);
512
0
  return status;
513
0
}
514
515
int freerdp_client_settings_write_connection_file(const rdpSettings* settings, const char* filename,
516
                                                  BOOL unicode)
517
0
{
518
0
  rdpFile* file = nullptr;
519
0
  int ret = -1;
520
0
  file = freerdp_client_rdp_file_new();
521
522
0
  if (!file)
523
0
    return -1;
524
525
0
  if (!freerdp_client_populate_rdp_file_from_settings(file, settings))
526
0
    goto out;
527
528
0
  if (!freerdp_client_write_rdp_file(file, filename, unicode))
529
0
    goto out;
530
531
0
  ret = 0;
532
0
out:
533
0
  freerdp_client_rdp_file_free(file);
534
0
  return ret;
535
0
}
536
537
int freerdp_client_settings_parse_assistance_file(rdpSettings* settings, int argc, char* argv[])
538
0
{
539
0
  int status = 0;
540
0
  int ret = -1;
541
0
  char* filename = nullptr;
542
0
  char* password = nullptr;
543
0
  rdpAssistanceFile* file = nullptr;
544
545
0
  if (!settings || !argv || (argc < 2))
546
0
    return -1;
547
548
0
  filename = argv[1];
549
550
0
  for (int x = 2; x < argc; x++)
551
0
  {
552
0
    const char* key = strstr(argv[x], "assistance:");
553
554
0
    if (key)
555
0
    {
556
0
      char* sep = strchr(key, ':');
557
0
      if (!sep)
558
0
        return -1;
559
560
0
      password = sep + 1;
561
0
    }
562
0
  }
563
564
0
  file = freerdp_assistance_file_new();
565
566
0
  if (!file)
567
0
    return -1;
568
569
0
  status = freerdp_assistance_parse_file(file, filename, password);
570
571
0
  if (status < 0)
572
0
    goto out;
573
574
0
  if (!freerdp_assistance_populate_settings_from_assistance_file(file, settings))
575
0
    goto out;
576
577
0
  ret = 0;
578
0
out:
579
0
  freerdp_assistance_file_free(file);
580
0
  return ret;
581
0
}
582
583
WINPR_ATTR_NODISCARD
584
static int client_cli_read_string(freerdp* instance, const char* what, const char* suggestion,
585
                                  char** result)
586
0
{
587
0
  WINPR_ASSERT(instance);
588
0
  WINPR_ASSERT(what);
589
0
  WINPR_ASSERT(result);
590
591
0
  size_t size = 0;
592
0
  printf("%s", what);
593
0
  (void)fflush(stdout);
594
595
0
  char* line = nullptr;
596
0
  if (suggestion && strlen(suggestion) > 0)
597
0
  {
598
0
    line = _strdup(suggestion);
599
0
    size = strlen(suggestion);
600
0
  }
601
602
0
  const SSIZE_T rc = freerdp_interruptible_get_line(instance->context, &line, &size, stdin);
603
0
  if (rc < 0)
604
0
  {
605
0
    char ebuffer[256] = WINPR_C_ARRAY_INIT;
606
0
    WLog_ERR(TAG, "freerdp_interruptible_get_line returned %s [%d]",
607
0
             winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
608
0
    free(line);
609
0
    return -1;
610
0
  }
611
612
0
  free(*result);
613
0
  *result = nullptr;
614
615
0
  if (line)
616
0
  {
617
0
    line = StrSep(&line, "\r");
618
0
    line = StrSep(&line, "\n");
619
0
    *result = line;
620
0
  }
621
0
  return 0;
622
0
}
623
624
/** @brief Callback set in the rdp_freerdp structure, and used to get the user's password,
625
 *  if required to establish the connection.
626
 *  This function is actually called in credssp_ntlmssp_client_init()
627
 *
628
 *  @see rdp_server_accept_nego() and rdp_check_fds()
629
 *  @param instance pointer to the rdp_freerdp structure that contains the connection settings
630
 *  @param username on input can contain a suggestion (must be allocated and is released by \b free
631
 * ). On output the allocated username entered by the user.
632
 *  @param password on input can contain a suggestion (must be allocated and is released by \b free
633
 * ). On output the allocated password entered by the user.
634
 *  @param domain on input can contain a suggestion (must be allocated and is released by \b free
635
 * ). On output the allocated domain entered by the user.
636
 *  @return TRUE if a password was successfully entered. See freerdp_passphrase_read() for more
637
 * details.
638
 */
639
WINPR_ATTR_NODISCARD
640
static BOOL client_cli_authenticate_raw(freerdp* instance, rdp_auth_reason reason, char** username,
641
                                        char** password, char** domain)
642
0
{
643
0
  static const size_t password_size = 512;
644
0
  const char* userAuth = "Username:        ";
645
0
  const char* domainAuth = "Domain:          ";
646
0
  const char* pwdAuth = "Password:        ";
647
0
  BOOL pinOnly = FALSE;
648
0
  BOOL queryAll = FALSE;
649
650
0
  WINPR_ASSERT(instance);
651
0
  WINPR_ASSERT(instance->context);
652
0
  WINPR_ASSERT(instance->context->settings);
653
654
0
  switch (reason)
655
0
  {
656
0
    case AUTH_SMARTCARD_PIN:
657
0
      pwdAuth = "Smartcard-Pin:   ";
658
0
      pinOnly = TRUE;
659
0
      break;
660
0
    case AUTH_FIDO_PIN:
661
0
      pwdAuth = "FIDO2 PIN:       ";
662
0
      pinOnly = TRUE;
663
0
      break;
664
0
    case AUTH_RDSTLS:
665
0
      queryAll = TRUE;
666
0
      break;
667
0
    case AUTH_TLS:
668
0
    case AUTH_RDP:
669
0
    case AUTH_NLA:
670
0
      break;
671
0
    case GW_AUTH_HTTP:
672
0
    case GW_AUTH_RDG:
673
0
    case GW_AUTH_RPC:
674
0
      userAuth = "GatewayUsername: ";
675
0
      domainAuth = "GatewayDomain:   ";
676
0
      pwdAuth = "GatewayPassword: ";
677
0
      break;
678
0
    default:
679
0
      return FALSE;
680
0
  }
681
682
0
  if (!username || !password || !domain)
683
0
    return FALSE;
684
685
0
  if (!pinOnly)
686
0
  {
687
0
    const char* suggest = *username;
688
0
    if (queryAll || !suggest)
689
0
    {
690
0
      const int rc = client_cli_read_string(instance, userAuth, suggest, username);
691
0
      if (rc < 0)
692
0
        goto fail;
693
0
    }
694
0
  }
695
696
0
  if (!pinOnly)
697
0
  {
698
0
    const char* suggest = *domain;
699
0
    if (queryAll || !suggest)
700
0
    {
701
0
      const int rc = client_cli_read_string(instance, domainAuth, suggest, domain);
702
0
      if (rc < 0)
703
0
        goto fail;
704
0
    }
705
0
  }
706
707
0
  {
708
0
    char* line = calloc(password_size, sizeof(char));
709
710
0
    if (!line)
711
0
      goto fail;
712
713
0
    const BOOL fromStdin =
714
0
        freerdp_settings_get_bool(instance->context->settings, FreeRDP_CredentialsFromStdin);
715
0
    const char* rc =
716
0
        freerdp_passphrase_read(instance->context, pwdAuth, line, password_size, fromStdin);
717
0
    if (rc == nullptr)
718
0
      goto fail;
719
720
0
    if (password_size > 0)
721
0
    {
722
0
      free(*password);
723
0
      *password = line;
724
0
    }
725
0
  }
726
727
0
  return TRUE;
728
0
fail:
729
0
  free(*username);
730
0
  free(*domain);
731
0
  free(*password);
732
0
  *username = nullptr;
733
0
  *domain = nullptr;
734
0
  *password = nullptr;
735
0
  return FALSE;
736
0
}
737
738
BOOL client_cli_authenticate_ex(freerdp* instance, char** username, char** password, char** domain,
739
                                rdp_auth_reason reason)
740
0
{
741
0
  WINPR_ASSERT(instance);
742
0
  WINPR_ASSERT(username);
743
0
  WINPR_ASSERT(password);
744
0
  WINPR_ASSERT(domain);
745
746
0
  switch (reason)
747
0
  {
748
0
    case AUTH_RDSTLS:
749
0
    case AUTH_NLA:
750
0
      break;
751
752
0
    case AUTH_TLS:
753
0
    case AUTH_RDP:
754
0
    case AUTH_SMARTCARD_PIN: /* in this case password is pin code */
755
0
    case AUTH_FIDO_PIN:
756
0
      if ((*username) && (*password))
757
0
        return TRUE;
758
0
      break;
759
0
    case GW_AUTH_HTTP:
760
0
    case GW_AUTH_RDG:
761
0
    case GW_AUTH_RPC:
762
0
      break;
763
0
    default:
764
0
      return FALSE;
765
0
  }
766
767
0
  return client_cli_authenticate_raw(instance, reason, username, password, domain);
768
0
}
769
770
BOOL client_cli_choose_smartcard(WINPR_ATTR_UNUSED freerdp* instance, SmartcardCertInfo** cert_list,
771
                                 DWORD count, DWORD* choice, BOOL gateway)
772
0
{
773
0
  unsigned long answer = 0;
774
0
  char* p = nullptr;
775
776
0
  printf("Multiple smartcards are available for use:\n");
777
0
  for (DWORD i = 0; i < count; i++)
778
0
  {
779
0
    const SmartcardCertInfo* cert = cert_list[i];
780
0
    char* reader = ConvertWCharToUtf8Alloc(cert->reader, nullptr);
781
0
    char* container_name = ConvertWCharToUtf8Alloc(cert->containerName, nullptr);
782
783
0
    printf("[%" PRIu32
784
0
           "] %s\n\tReader: %s\n\tUser: %s@%s\n\tSubject: %s\n\tIssuer: %s\n\tUPN: %s\n",
785
0
           i, container_name, reader, cert->userHint, cert->domainHint, cert->subject,
786
0
           cert->issuer, cert->upn);
787
788
0
    free(reader);
789
0
    free(container_name);
790
0
  }
791
792
0
  while (1)
793
0
  {
794
0
    char input[10] = WINPR_C_ARRAY_INIT;
795
796
0
    printf("\nChoose a smartcard to use for %s (0 - %" PRIu32 "): ",
797
0
           gateway ? "gateway authentication" : "logon", count - 1);
798
0
    (void)fflush(stdout);
799
0
    if (!fgets(input, 10, stdin))
800
0
    {
801
0
      WLog_ERR(TAG, "could not read from stdin");
802
0
      return FALSE;
803
0
    }
804
805
0
    answer = strtoul(input, &p, 10);
806
0
    if ((*p == '\n' && p != input) && answer < count)
807
0
    {
808
0
      *choice = (UINT32)answer;
809
0
      return TRUE;
810
0
    }
811
0
  }
812
0
}
813
814
#if defined(WITH_FREERDP_DEPRECATED)
815
BOOL client_cli_authenticate(freerdp* instance, char** username, char** password, char** domain)
816
{
817
  if (freerdp_settings_get_bool(instance->settings, FreeRDP_SmartcardLogon))
818
  {
819
    WLog_INFO(TAG, "Authentication via smartcard");
820
    return TRUE;
821
  }
822
823
  return client_cli_authenticate_raw(instance, FALSE, username, password, domain);
824
}
825
826
BOOL client_cli_gw_authenticate(freerdp* instance, char** username, char** password, char** domain)
827
{
828
  return client_cli_authenticate_raw(instance, TRUE, username, password, domain);
829
}
830
#endif
831
832
WINPR_ATTR_NODISCARD
833
static DWORD client_cli_accept_certificate(freerdp* instance)
834
0
{
835
0
  int answer = 0;
836
837
0
  WINPR_ASSERT(instance);
838
0
  WINPR_ASSERT(instance->context);
839
840
0
  const rdpSettings* settings = instance->context->settings;
841
0
  WINPR_ASSERT(settings);
842
843
0
  const BOOL fromStdin = freerdp_settings_get_bool(settings, FreeRDP_CredentialsFromStdin);
844
0
  if (fromStdin)
845
0
    return 0;
846
847
0
  while (1)
848
0
  {
849
0
    printf("Do you trust the above certificate? (Y/T/N) ");
850
0
    (void)fflush(stdout);
851
0
    answer = freerdp_interruptible_getc(instance->context, stdin);
852
853
0
    if ((answer == EOF) || feof(stdin))
854
0
    {
855
0
      printf("\nError: Could not read answer from stdin.\n");
856
0
      return 0;
857
0
    }
858
859
0
    switch (answer)
860
0
    {
861
0
      case 'y':
862
0
      case 'Y':
863
0
        answer = freerdp_interruptible_getc(instance->context, stdin);
864
0
        printf("\n");
865
0
        if (answer == EOF)
866
0
          return 0;
867
0
        return 1;
868
869
0
      case 't':
870
0
      case 'T':
871
0
        answer = freerdp_interruptible_getc(instance->context, stdin);
872
0
        printf("\n");
873
0
        if (answer == EOF)
874
0
          return 0;
875
0
        return 2;
876
877
0
      case 'n':
878
0
      case 'N':
879
0
        answer = freerdp_interruptible_getc(instance->context, stdin);
880
0
        printf("\n");
881
0
        if (answer == EOF)
882
0
          return 0;
883
0
        return 0;
884
885
0
      default:
886
0
        break;
887
0
    }
888
0
  }
889
0
}
890
891
/** Callback set in the rdp_freerdp structure, and used to make a certificate validation
892
 *  when the connection requires it.
893
 *  This function will actually be called by tls_verify_certificate().
894
 *  @see rdp_client_connect() and freerdp_tls_connect()
895
 *  @deprecated Use client_cli_verify_certificate_ex
896
 *  @param instance - pointer to the rdp_freerdp structure that contains the connection settings
897
 *  @param common_name
898
 *  @param subject
899
 *  @param issuer
900
 *  @param fingerprint
901
 *  @param host_mismatch Indicates the certificate host does not match.
902
 *  @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
903
 */
904
#if defined(WITH_FREERDP_DEPRECATED)
905
DWORD client_cli_verify_certificate(freerdp* instance, const char* common_name, const char* subject,
906
                                    const char* issuer, const char* fingerprint, BOOL host_mismatch)
907
{
908
  WINPR_UNUSED(common_name);
909
  WINPR_UNUSED(host_mismatch);
910
911
  printf("WARNING: This callback is deprecated, migrate to client_cli_verify_certificate_ex\n");
912
  printf("Certificate details:\n");
913
  printf("\tSubject: %s\n", subject);
914
  printf("\tIssuer: %s\n", issuer);
915
  printf("\tThumbprint: %s\n", fingerprint);
916
  printf("The above X.509 certificate could not be verified, possibly because you do not have\n"
917
         "the CA certificate in your certificate store, or the certificate has expired.\n"
918
         "Please look at the OpenSSL documentation on how to add a private CA to the store.\n");
919
  return client_cli_accept_certificate(instance);
920
}
921
#endif
922
923
WINPR_ATTR_MALLOC(free, 1)
924
static char* client_cli_pem_cert(const char* pem)
925
0
{
926
0
  rdpCertificate* cert = freerdp_certificate_new_from_pem(pem);
927
0
  if (!cert)
928
0
    return nullptr;
929
930
0
  char* fp = freerdp_certificate_get_fingerprint(cert);
931
0
  char* start = freerdp_certificate_get_validity(cert, TRUE);
932
0
  char* end = freerdp_certificate_get_validity(cert, FALSE);
933
0
  freerdp_certificate_free(cert);
934
935
0
  char* str = nullptr;
936
0
  size_t slen = 0;
937
0
  winpr_asprintf(&str, &slen,
938
0
                 "\tValid from:  %s\n"
939
0
                 "\tValid to:    %s\n"
940
0
                 "\tThumbprint:  %s\n",
941
0
                 start, end, fp);
942
0
  free(fp);
943
0
  free(start);
944
0
  free(end);
945
0
  return str;
946
0
}
947
948
/** Callback set in the rdp_freerdp structure, and used to make a certificate validation
949
 *  when the connection requires it.
950
 *  This function will actually be called by tls_verify_certificate().
951
 *  @see rdp_client_connect() and freerdp_tls_connect()
952
 *  @param instance     pointer to the rdp_freerdp structure that contains the connection settings
953
 *  @param host         The host currently connecting to
954
 *  @param port         The port currently connecting to
955
 *  @param common_name  The common name of the certificate, should match host or an alias of it
956
 *  @param subject      The subject of the certificate
957
 *  @param issuer       The certificate issuer name
958
 *  @param fingerprint  The fingerprint of the certificate
959
 *  @param flags        See VERIFY_CERT_FLAG_* for possible values.
960
 *
961
 *  @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
962
 */
963
DWORD client_cli_verify_certificate_ex(freerdp* instance, const char* host, UINT16 port,
964
                                       const char* common_name, const char* subject,
965
                                       const char* issuer, const char* fingerprint, DWORD flags)
966
0
{
967
0
  const char* type = "RDP-Server";
968
969
0
  WINPR_ASSERT(instance);
970
0
  WINPR_ASSERT(instance->context);
971
0
  WINPR_ASSERT(instance->context->settings);
972
973
0
  if (flags & VERIFY_CERT_FLAG_GATEWAY)
974
0
    type = "RDP-Gateway";
975
976
0
  if (flags & VERIFY_CERT_FLAG_REDIRECT)
977
0
    type = "RDP-Redirect";
978
979
0
  printf("Certificate details for %s:%" PRIu16 " (%s):\n", host, port, type);
980
0
  printf("\tCommon Name: %s\n", common_name);
981
0
  printf("\tSubject:     %s\n", subject);
982
0
  printf("\tIssuer:      %s\n", issuer);
983
  /* Newer versions of FreeRDP allow exposing the whole PEM by setting
984
   * FreeRDP_CertificateCallbackPreferPEM to TRUE
985
   */
986
0
  if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
987
0
  {
988
0
    char* str = client_cli_pem_cert(fingerprint);
989
0
    printf("%s", str);
990
0
    free(str);
991
0
  }
992
0
  else
993
0
    printf("\tThumbprint:  %s\n", fingerprint);
994
995
0
  printf("The above X.509 certificate could not be verified, possibly because you do not have\n"
996
0
         "the CA certificate in your certificate store, or the certificate has expired.\n"
997
0
         "Please look at the OpenSSL documentation on how to add a private CA to the store.\n");
998
0
  return client_cli_accept_certificate(instance);
999
0
}
1000
1001
/** Callback set in the rdp_freerdp structure, and used to make a certificate validation
1002
 *  when a stored certificate does not match the remote counterpart.
1003
 *  This function will actually be called by tls_verify_certificate().
1004
 *  @see rdp_client_connect() and freerdp_tls_connect()
1005
 *  @deprecated Use client_cli_verify_changed_certificate_ex
1006
 *  @param instance - pointer to the rdp_freerdp structure that contains the connection settings
1007
 *  @param common_name
1008
 *  @param subject
1009
 *  @param issuer
1010
 *  @param fingerprint
1011
 *  @param old_subject
1012
 *  @param old_issuer
1013
 *  @param old_fingerprint
1014
 *  @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
1015
 */
1016
#if defined(WITH_FREERDP_DEPRECATED)
1017
DWORD client_cli_verify_changed_certificate(freerdp* instance, const char* common_name,
1018
                                            const char* subject, const char* issuer,
1019
                                            const char* fingerprint, const char* old_subject,
1020
                                            const char* old_issuer, const char* old_fingerprint)
1021
{
1022
  WINPR_UNUSED(common_name);
1023
1024
  printf("WARNING: This callback is deprecated, migrate to "
1025
         "client_cli_verify_changed_certificate_ex\n");
1026
  printf("!!! Certificate has changed !!!\n");
1027
  printf("\n");
1028
  printf("New Certificate details:\n");
1029
  printf("\tSubject: %s\n", subject);
1030
  printf("\tIssuer: %s\n", issuer);
1031
  printf("\tThumbprint: %s\n", fingerprint);
1032
  printf("\n");
1033
  printf("Old Certificate details:\n");
1034
  printf("\tSubject: %s\n", old_subject);
1035
  printf("\tIssuer: %s\n", old_issuer);
1036
  printf("\tThumbprint: %s\n", old_fingerprint);
1037
  printf("\n");
1038
  printf("The above X.509 certificate does not match the certificate used for previous "
1039
         "connections.\n"
1040
         "This may indicate that the certificate has been tampered with.\n"
1041
         "Please contact the administrator of the RDP server and clarify.\n");
1042
  return client_cli_accept_certificate(instance);
1043
}
1044
#endif
1045
1046
/** Callback set in the rdp_freerdp structure, and used to make a certificate validation
1047
 *  when a stored certificate does not match the remote counterpart.
1048
 *  This function will actually be called by tls_verify_certificate().
1049
 *  @see rdp_client_connect() and freerdp_tls_connect()
1050
 *  @param instance        pointer to the rdp_freerdp structure that contains the connection
1051
 * settings
1052
 *  @param host            The host currently connecting to
1053
 *  @param port            The port currently connecting to
1054
 *  @param common_name     The common name of the certificate, should match host or an alias of it
1055
 *  @param subject         The subject of the certificate
1056
 *  @param issuer          The certificate issuer name
1057
 *  @param fingerprint     The fingerprint of the certificate
1058
 *  @param old_subject     The subject of the previous certificate
1059
 *  @param old_issuer      The previous certificate issuer name
1060
 *  @param old_fingerprint The fingerprint of the previous certificate
1061
 *  @param flags           See VERIFY_CERT_FLAG_* for possible values.
1062
 *
1063
 *  @return 1 if the certificate is trusted, 2 if temporary trusted, 0 otherwise.
1064
 */
1065
DWORD client_cli_verify_changed_certificate_ex(freerdp* instance, const char* host, UINT16 port,
1066
                                               const char* common_name, const char* subject,
1067
                                               const char* issuer, const char* fingerprint,
1068
                                               const char* old_subject, const char* old_issuer,
1069
                                               const char* old_fingerprint, DWORD flags)
1070
0
{
1071
0
  const char* type = "RDP-Server";
1072
1073
0
  WINPR_ASSERT(instance);
1074
0
  WINPR_ASSERT(instance->context);
1075
0
  WINPR_ASSERT(instance->context->settings);
1076
1077
0
  if (flags & VERIFY_CERT_FLAG_GATEWAY)
1078
0
    type = "RDP-Gateway";
1079
1080
0
  if (flags & VERIFY_CERT_FLAG_REDIRECT)
1081
0
    type = "RDP-Redirect";
1082
1083
0
  printf("!!!Certificate for %s:%" PRIu16 " (%s) has changed!!!\n", host, port, type);
1084
0
  printf("\n");
1085
0
  printf("New Certificate details:\n");
1086
0
  printf("\tCommon Name: %s\n", common_name);
1087
0
  printf("\tSubject:     %s\n", subject);
1088
0
  printf("\tIssuer:      %s\n", issuer);
1089
  /* Newer versions of FreeRDP allow exposing the whole PEM by setting
1090
   * FreeRDP_CertificateCallbackPreferPEM to TRUE
1091
   */
1092
0
  if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
1093
0
  {
1094
0
    char* str = client_cli_pem_cert(fingerprint);
1095
0
    printf("%s", str);
1096
0
    free(str);
1097
0
  }
1098
0
  else
1099
0
    printf("\tThumbprint:  %s\n", fingerprint);
1100
0
  printf("\n");
1101
0
  printf("Old Certificate details:\n");
1102
0
  printf("\tSubject:     %s\n", old_subject);
1103
0
  printf("\tIssuer:      %s\n", old_issuer);
1104
  /* Newer versions of FreeRDP allow exposing the whole PEM by setting
1105
   * FreeRDP_CertificateCallbackPreferPEM to TRUE
1106
   */
1107
0
  if (flags & VERIFY_CERT_FLAG_FP_IS_PEM)
1108
0
  {
1109
0
    char* str = client_cli_pem_cert(old_fingerprint);
1110
0
    printf("%s", str);
1111
0
    free(str);
1112
0
  }
1113
0
  else
1114
0
    printf("\tThumbprint:  %s\n", old_fingerprint);
1115
0
  printf("\n");
1116
0
  if (flags & VERIFY_CERT_FLAG_MATCH_LEGACY_SHA1)
1117
0
  {
1118
0
    printf("\tA matching entry with legacy SHA1 was found in local known_hosts2 store.\n");
1119
0
    printf("\tIf you just upgraded from a FreeRDP version before 2.0 this is expected.\n");
1120
0
    printf("\tThe hashing algorithm has been upgraded from SHA1 to SHA256.\n");
1121
0
    printf("\tAll manually accepted certificates must be reconfirmed!\n");
1122
0
    printf("\n");
1123
0
  }
1124
0
  printf("The above X.509 certificate does not match the certificate used for previous "
1125
0
         "connections.\n"
1126
0
         "This may indicate that the certificate has been tampered with.\n"
1127
0
         "Please contact the administrator of the RDP server and clarify.\n");
1128
0
  return client_cli_accept_certificate(instance);
1129
0
}
1130
1131
BOOL client_cli_present_gateway_message(freerdp* instance, UINT32 type, BOOL isDisplayMandatory,
1132
                                        BOOL isConsentMandatory, size_t length,
1133
                                        const WCHAR* message)
1134
0
{
1135
0
  const char* msgType = (type == GATEWAY_MESSAGE_CONSENT) ? "Consent message" : "Service message";
1136
1137
0
  WINPR_ASSERT(instance);
1138
0
  WINPR_ASSERT(instance->context);
1139
0
  WINPR_ASSERT(instance->context->settings);
1140
1141
0
  if (!isDisplayMandatory && !isConsentMandatory)
1142
0
    return TRUE;
1143
1144
0
  printf("%s:\n", msgType);
1145
0
  {
1146
0
    LPSTR msg = ConvertWCharNToUtf8Alloc(message, length / sizeof(WCHAR), nullptr);
1147
0
    if (!msg)
1148
0
    {
1149
0
      printf("Failed to convert message!\n");
1150
0
      return FALSE;
1151
0
    }
1152
0
    printf("%s\n", msg);
1153
0
    free(msg);
1154
0
  }
1155
1156
0
  while (isConsentMandatory)
1157
0
  {
1158
0
    printf("I understand and agree to the terms of this policy (Y/N) \n");
1159
0
    (void)fflush(stdout);
1160
0
    const int answer = freerdp_interruptible_getc(instance->context, stdin);
1161
1162
0
    if ((answer == EOF) || feof(stdin))
1163
0
    {
1164
0
      printf("\nError: Could not read answer from stdin.\n");
1165
0
      return FALSE;
1166
0
    }
1167
1168
0
    const int confirm = freerdp_interruptible_getc(instance->context, stdin);
1169
0
    switch (answer)
1170
0
    {
1171
0
      case 'y':
1172
0
      case 'Y':
1173
0
        printf("\n");
1174
0
        return confirm != EOF;
1175
1176
0
      case 'n':
1177
0
      case 'N':
1178
0
        printf("\n");
1179
0
        return FALSE;
1180
1181
0
      default:
1182
0
        break;
1183
0
    }
1184
0
  }
1185
1186
0
  return TRUE;
1187
0
}
1188
1189
#if defined(WITH_AAD)
1190
WINPR_ATTR_NODISCARD
1191
static BOOL client_cli_get_rdsaad_access_token(freerdp* instance, const char* scope,
1192
                                               const char* req_cnf, char** token)
1193
{
1194
  WINPR_ASSERT(instance);
1195
  WINPR_ASSERT(instance->context);
1196
1197
  size_t size = 0;
1198
  char* url = nullptr;
1199
  char* token_request = nullptr;
1200
1201
  WINPR_ASSERT(scope);
1202
  WINPR_ASSERT(req_cnf);
1203
  WINPR_ASSERT(token);
1204
1205
  BOOL rc = FALSE;
1206
  *token = nullptr;
1207
1208
  WINPR_ASSERT(requireRdpClientContext(instance->context));
1209
  if (!requireRdpClientContext(instance->context))
1210
    return FALSE;
1211
1212
  char* request = freerdp_client_get_aad_url((rdpClientContext*)instance->context,
1213
                                             FREERDP_CLIENT_AAD_AUTH_REQUEST, scope);
1214
1215
  printf("Browse to: %s\n", request);
1216
  winpr_zfree(request);
1217
  printf("Paste redirect URL here: \n");
1218
1219
  if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0)
1220
    goto cleanup;
1221
1222
  {
1223
    char* code =
1224
        freerdp_client_extract_aad_code((rdpClientContext*)instance->context, url, size);
1225
    if (!code)
1226
      goto cleanup;
1227
    token_request =
1228
        freerdp_client_get_aad_url((rdpClientContext*)instance->context,
1229
                                   FREERDP_CLIENT_AAD_TOKEN_REQUEST, scope, code, req_cnf);
1230
    winpr_zfree(code);
1231
  }
1232
  if (!token_request)
1233
    goto cleanup;
1234
1235
  rc = client_common_get_access_token(instance, token_request, token);
1236
1237
cleanup:
1238
  winpr_zfree(token_request);
1239
  free(url);
1240
  return rc && (*token != nullptr);
1241
}
1242
1243
WINPR_ATTR_NODISCARD
1244
static BOOL client_cli_get_avd_access_token(freerdp* instance, char** token)
1245
{
1246
  WINPR_ASSERT(instance);
1247
  WINPR_ASSERT(instance->context);
1248
1249
  WINPR_ASSERT(requireRdpClientContext(instance->context));
1250
  if (!requireRdpClientContext(instance->context))
1251
    return FALSE;
1252
1253
  size_t size = 0;
1254
  char* url = nullptr;
1255
  char* token_request = nullptr;
1256
1257
  WINPR_ASSERT(token);
1258
1259
  BOOL rc = FALSE;
1260
1261
  *token = nullptr;
1262
1263
  char* request = freerdp_client_get_aad_url((rdpClientContext*)instance->context,
1264
                                             FREERDP_CLIENT_AAD_AVD_AUTH_REQUEST);
1265
  if (!request)
1266
    return FALSE;
1267
  printf("Browse to: %s\n", request);
1268
  winpr_zfree(request);
1269
  printf("Paste redirect URL here: \n");
1270
1271
  if (freerdp_interruptible_get_line(instance->context, &url, &size, stdin) < 0)
1272
    goto cleanup;
1273
1274
  {
1275
    char* code =
1276
        freerdp_client_extract_aad_code((rdpClientContext*)instance->context, url, size);
1277
    if (!code)
1278
      goto cleanup;
1279
    token_request = freerdp_client_get_aad_url((rdpClientContext*)instance->context,
1280
                                               FREERDP_CLIENT_AAD_AVD_TOKEN_REQUEST, code);
1281
    winpr_zfree(code);
1282
  }
1283
1284
  if (!token_request)
1285
    goto cleanup;
1286
1287
  rc = client_common_get_access_token(instance, token_request, token);
1288
1289
cleanup:
1290
  free(token_request);
1291
  free(url);
1292
  return rc && (*token != nullptr);
1293
}
1294
#endif
1295
1296
WINPR_ATTR_NODISCARD
1297
static BOOL client_cli_get_access_token_va(freerdp* instance,
1298
                                           WINPR_ATTR_UNUSED AccessTokenType tokenType,
1299
                                           char** token, WINPR_ATTR_UNUSED size_t count, va_list ap)
1300
0
{
1301
0
  WINPR_ASSERT(instance);
1302
0
  WINPR_ASSERT(token);
1303
1304
0
  *token = nullptr;
1305
0
#if !defined(WITH_AAD)
1306
0
  WLog_ERR(TAG, "Build does not support AAD authentication");
1307
0
  return FALSE;
1308
#else
1309
  BOOL rc = FALSE;
1310
  WINPR_ASSERT(instance->context);
1311
  const BOOL saved =
1312
      freerdp_settings_get_bool(instance->context->settings, FreeRDP_UseCommonStdioCallbacks);
1313
  if (!freerdp_settings_set_bool(instance->context->settings, FreeRDP_UseCommonStdioCallbacks,
1314
                                 TRUE))
1315
    return FALSE;
1316
1317
  switch (tokenType)
1318
  {
1319
    case ACCESS_TOKEN_TYPE_AAD:
1320
    {
1321
      if (count < 2)
1322
      {
1323
        WLog_ERR(TAG,
1324
                 "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
1325
                 ", aborting",
1326
                 count);
1327
      }
1328
      else
1329
      {
1330
        if (count > 2)
1331
          WLog_WARN(
1332
              TAG,
1333
              "ACCESS_TOKEN_TYPE_AAD expected 2 additional arguments, but got %" PRIuz
1334
              ", ignoring",
1335
              count);
1336
        const char* scope = va_arg(ap, const char*);
1337
        const char* req_cnf = va_arg(ap, const char*);
1338
        rc = client_cli_get_rdsaad_access_token(instance, scope, req_cnf, token);
1339
      }
1340
    }
1341
    break;
1342
    case ACCESS_TOKEN_TYPE_AVD:
1343
      if (count != 0)
1344
        WLog_WARN(TAG,
1345
                  "ACCESS_TOKEN_TYPE_AVD expected 0 additional arguments, but got %" PRIuz
1346
                  ", ignoring",
1347
                  count);
1348
      rc = client_cli_get_avd_access_token(instance, token);
1349
      break;
1350
    default:
1351
      WLog_ERR(TAG, "Unexpected value for AccessTokenType [%u], aborting", tokenType);
1352
      break;
1353
  }
1354
1355
  if (!freerdp_settings_set_bool(instance->context->settings, FreeRDP_UseCommonStdioCallbacks,
1356
                                 saved))
1357
    return FALSE;
1358
  return rc;
1359
#endif
1360
0
}
1361
1362
BOOL client_cli_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
1363
                                 size_t count, ...)
1364
0
{
1365
0
  va_list ap = WINPR_C_ARRAY_INIT;
1366
0
  va_start(ap, count);
1367
0
  const BOOL rc = client_cli_get_access_token_va(instance, tokenType, token, count, ap);
1368
0
  va_end(ap);
1369
0
  return rc;
1370
0
}
1371
1372
BOOL client_common_get_access_token(WINPR_ATTR_UNUSED freerdp* instance,
1373
                                    WINPR_ATTR_UNUSED const char* request,
1374
                                    WINPR_ATTR_UNUSED char** token)
1375
0
{
1376
0
  WINPR_ASSERT(request);
1377
0
  WINPR_ASSERT(token);
1378
1379
0
  *token = nullptr;
1380
#ifdef WITH_AAD
1381
  BOOL ret = FALSE;
1382
  long resp_code = 0;
1383
  BYTE* response = nullptr;
1384
  size_t response_length = 0;
1385
1386
  wLog* log = WLog_Get(TAG);
1387
1388
  const char* token_ep =
1389
      freerdp_utils_aad_get_wellknown_string(instance->context, AAD_WELLKNOWN_token_endpoint);
1390
  if (!freerdp_http_request(token_ep, request, &resp_code, &response, &response_length))
1391
  {
1392
    WLog_ERR(TAG, "access token request failed");
1393
    return FALSE;
1394
  }
1395
1396
  if (resp_code != HTTP_STATUS_OK)
1397
  {
1398
    char buffer[64] = WINPR_C_ARRAY_INIT;
1399
1400
    WLog_Print(log, WLOG_ERROR,
1401
               "Server unwilling to provide access token; returned status code %s",
1402
               freerdp_http_status_string_format(resp_code, buffer, sizeof(buffer)));
1403
    if (response_length > 0)
1404
      WLog_Print(log, WLOG_ERROR, "[status message] %s", response);
1405
    goto cleanup;
1406
  }
1407
1408
  *token = freerdp_utils_aad_get_access_token(log, (const char*)response, response_length);
1409
  if (*token)
1410
    ret = TRUE;
1411
1412
cleanup:
1413
  free(response);
1414
  return ret;
1415
#else
1416
0
  WLog_ERR(TAG, "Build does not support AAD authentication");
1417
0
  return FALSE;
1418
0
#endif
1419
0
}
1420
1421
SSIZE_T client_common_retry_dialog(freerdp* instance, const char* what, size_t current,
1422
                                   void* userarg)
1423
0
{
1424
0
  WINPR_UNUSED(instance);
1425
0
  WINPR_ASSERT(instance->context);
1426
0
  WINPR_UNUSED(userarg);
1427
0
  WINPR_ASSERT(instance);
1428
0
  WINPR_ASSERT(what);
1429
1430
0
  if ((strcmp(what, "arm-transport") != 0) && (strcmp(what, "connection") != 0))
1431
0
  {
1432
0
    WLog_ERR(TAG, "Unknown module %s, aborting", what);
1433
0
    return -1;
1434
0
  }
1435
1436
0
  if (current == 0)
1437
0
  {
1438
0
    if (strcmp(what, "arm-transport") == 0)
1439
0
      WLog_INFO(TAG, "[%s] Starting your VM. It may take up to 5 minutes", what);
1440
0
  }
1441
1442
0
  const rdpSettings* settings = instance->context->settings;
1443
0
  const BOOL enabled = freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled);
1444
0
  if (!enabled)
1445
0
  {
1446
0
    WLog_WARN(TAG, "Automatic reconnection disabled, terminating. Try to connect again later");
1447
0
    return -1;
1448
0
  }
1449
1450
0
  const size_t max = freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
1451
0
  const size_t delay = freerdp_settings_get_uint32(settings, FreeRDP_TcpConnectTimeout);
1452
0
  if (current >= max)
1453
0
  {
1454
0
    WLog_ERR(TAG,
1455
0
             "[%s] retries exceeded. Your VM failed to start. Try again later or contact your "
1456
0
             "tech support for help if this keeps happening.",
1457
0
             what);
1458
0
    return -1;
1459
0
  }
1460
1461
0
  WLog_INFO(TAG, "[%s] retry %" PRIuz "/%" PRIuz ", delaying %" PRIuz "ms before next attempt",
1462
0
            what, current + 1, max, delay);
1463
0
  return WINPR_ASSERTING_INT_CAST(SSIZE_T, delay);
1464
0
}
1465
1466
BOOL client_auto_reconnect(freerdp* instance)
1467
0
{
1468
0
  return client_auto_reconnect_ex(instance, nullptr);
1469
0
}
1470
1471
WINPR_ATTR_NODISCARD
1472
static BOOL client_auto_reconnect_do(freerdp* instance, BOOL retry, UINT32 numRetries,
1473
                                     UINT32 maxRetries)
1474
0
{
1475
0
  WINPR_ASSERT(instance);
1476
1477
  /* Perform an auto-reconnect. */
1478
0
  while (retry)
1479
0
  {
1480
    /* Quit retrying if max retries has been exceeded */
1481
0
    if ((maxRetries > 0) && (numRetries >= maxRetries))
1482
0
    {
1483
0
      WLog_DBG(TAG, "AutoReconnect retries exceeded.");
1484
0
      return FALSE;
1485
0
    }
1486
1487
    /* Attempt the next reconnect */
1488
0
    WLog_INFO(TAG, "Attempting reconnect (%" PRIu32 " of %" PRIu32 ")", numRetries, maxRetries);
1489
1490
0
    const SSIZE_T delay =
1491
0
        IFCALLRESULT(5000, instance->RetryDialog, instance, "connection", numRetries, nullptr);
1492
0
    if (delay < 0)
1493
0
      return FALSE;
1494
0
    numRetries++;
1495
1496
0
    if (freerdp_reconnect(instance))
1497
0
      return TRUE;
1498
1499
0
    switch (freerdp_get_last_error(instance->context))
1500
0
    {
1501
0
      case FREERDP_ERROR_CONNECT_CANCELLED:
1502
0
        WLog_WARN(TAG, "Autoreconnect aborted by user");
1503
0
        return FALSE;
1504
0
      default:
1505
0
        break;
1506
0
    }
1507
1508
0
    for (SSIZE_T x = 0; x < delay / 10; x++)
1509
0
      Sleep(10);
1510
0
  }
1511
1512
0
  WLog_ERR(TAG, "Maximum reconnect retries exceeded");
1513
0
  return FALSE;
1514
0
}
1515
1516
WINPR_ATTR_NODISCARD
1517
static DWORD WINAPI handle_window_events(void* parg)
1518
0
{
1519
0
  handle_window_events_t* arg = parg;
1520
0
  WINPR_ASSERT(arg);
1521
1522
0
  while (arg->running)
1523
0
  {
1524
0
    WINPR_ASSERT(arg->window_events);
1525
0
    if (!arg->window_events(arg->instance))
1526
0
    {
1527
0
      WLog_ERR(TAG, "window_events failed!");
1528
0
    }
1529
0
    Sleep(10);
1530
0
  }
1531
0
  return 0;
1532
0
}
1533
1534
BOOL client_auto_reconnect_ex(freerdp* instance, window_events_fkt_t window_events)
1535
0
{
1536
0
  BOOL retry = TRUE;
1537
0
  UINT32 error = 0;
1538
0
  UINT32 numRetries = 0;
1539
0
  rdpSettings* settings = nullptr;
1540
1541
0
  if (!instance)
1542
0
    return FALSE;
1543
1544
0
  WINPR_ASSERT(instance->context);
1545
1546
0
  settings = instance->context->settings;
1547
0
  WINPR_ASSERT(settings);
1548
1549
0
  const UINT32 maxRetries =
1550
0
      freerdp_settings_get_uint32(settings, FreeRDP_AutoReconnectMaxRetries);
1551
1552
  /* Only auto reconnect on network disconnects. */
1553
0
  error = freerdp_error_info(instance);
1554
0
  switch (error)
1555
0
  {
1556
0
    case ERRINFO_GRAPHICS_SUBSYSTEM_FAILED:
1557
      /* A network disconnect was detected */
1558
0
      WLog_WARN(TAG, "Disconnected by server hitting a bug or resource limit [%s]",
1559
0
                freerdp_get_error_info_string(error));
1560
0
      break;
1561
0
    case ERRINFO_SUCCESS:
1562
      /* A network disconnect was detected */
1563
0
      WLog_INFO(TAG, "Network disconnect!");
1564
0
      break;
1565
0
    default:
1566
0
      WLog_DBG(TAG, "Other error: %s", freerdp_get_error_info_string(error));
1567
0
      return FALSE;
1568
0
  }
1569
1570
0
  if (!freerdp_settings_get_bool(settings, FreeRDP_AutoReconnectionEnabled))
1571
0
  {
1572
    /* No auto-reconnect - just quit */
1573
0
    WLog_DBG(TAG, "AutoReconnect not enabled, quitting.");
1574
0
    return FALSE;
1575
0
  }
1576
1577
0
  const UINT err = freerdp_get_last_error(instance->context);
1578
0
  switch (err)
1579
0
  {
1580
0
    case FREERDP_ERROR_CONNECT_LOGON_FAILURE:
1581
0
    case FREERDP_ERROR_CONNECT_CLIENT_REVOKED:
1582
0
    case FREERDP_ERROR_CONNECT_WRONG_PASSWORD:
1583
0
    case FREERDP_ERROR_CONNECT_ACCESS_DENIED:
1584
0
    case FREERDP_ERROR_CONNECT_ACCOUNT_RESTRICTION:
1585
0
    case FREERDP_ERROR_CONNECT_ACCOUNT_LOCKED_OUT:
1586
0
    case FREERDP_ERROR_CONNECT_ACCOUNT_EXPIRED:
1587
0
    case FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS:
1588
0
      WLog_WARN(TAG, "Connection aborted: credentials do not work [%s]",
1589
0
                freerdp_get_last_error_name(err));
1590
0
      return FALSE;
1591
0
    case FREERDP_ERROR_CONNECT_CANCELLED:
1592
0
      WLog_WARN(TAG, "Connection aborted by user");
1593
0
      return FALSE;
1594
0
    default:
1595
0
      break;
1596
0
  }
1597
1598
0
  handle_window_events_t arg = { .instance = instance,
1599
0
                               .window_events = window_events,
1600
0
                               .running = true };
1601
0
  HANDLE thread = nullptr;
1602
0
  if (window_events)
1603
0
    thread = CreateThread(nullptr, 0, handle_window_events, &arg, 0, nullptr);
1604
0
  const BOOL rc = client_auto_reconnect_do(instance, retry, numRetries, maxRetries);
1605
0
  if (thread)
1606
0
  {
1607
0
    arg.running = FALSE;
1608
0
    WaitForSingleObject(thread, INFINITE);
1609
0
    CloseHandle(thread);
1610
0
  }
1611
0
  return rc;
1612
0
}
1613
1614
#if !defined(_WIN32)
1615
static BOOL common_inject_keypress(rdpContext* context, const char* buffer, size_t size)
1616
0
{
1617
0
  WCHAR wbuffer[64] = WINPR_C_ARRAY_INIT;
1618
0
  const SSIZE_T len = ConvertUtf8NToWChar(buffer, size, wbuffer, ARRAYSIZE(wbuffer));
1619
0
  if (len < 0)
1620
0
    return FALSE;
1621
1622
0
  rdpInput* input = context->input;
1623
0
  WINPR_ASSERT(input);
1624
1625
0
  for (SSIZE_T x = 0; x < len; x++)
1626
0
  {
1627
0
    const WCHAR code = wbuffer[x];
1628
0
    freerdp_input_send_unicode_keyboard_event(input, 0, code);
1629
0
    Sleep(5);
1630
0
    freerdp_input_send_unicode_keyboard_event(input, KBD_FLAGS_RELEASE, code);
1631
0
    Sleep(5);
1632
0
  }
1633
0
  return TRUE;
1634
0
}
1635
1636
static BOOL common_process_pipe(rdpContext* context, const char* pipe)
1637
0
{
1638
0
  int fd = open(pipe, O_NONBLOCK | O_RDONLY);
1639
0
  if (fd < 0)
1640
0
  {
1641
0
    char ebuffer[256] = WINPR_C_ARRAY_INIT;
1642
0
    WLog_ERR(TAG, "pipe '%s' open returned %s [%d]", pipe,
1643
0
             winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
1644
0
    return FALSE;
1645
0
  }
1646
0
  while (!freerdp_shall_disconnect_context(context))
1647
0
  {
1648
0
    char buffer[64] = WINPR_C_ARRAY_INIT;
1649
0
    errno = 0;
1650
0
    ssize_t rd = read(fd, buffer, sizeof(buffer) - 1);
1651
0
    if (rd == 0)
1652
0
    {
1653
0
      char ebuffer[256] = WINPR_C_ARRAY_INIT;
1654
0
      if ((errno == EAGAIN) || (errno == 0))
1655
0
      {
1656
0
        Sleep(100);
1657
0
        continue;
1658
0
      }
1659
1660
      // EOF, abort reading.
1661
0
      WLog_ERR(TAG, "pipe '%s' read returned %s [%d]", pipe,
1662
0
               winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
1663
0
      break;
1664
0
    }
1665
0
    else if (rd < 0)
1666
0
    {
1667
0
      char ebuffer[256] = WINPR_C_ARRAY_INIT;
1668
0
      WLog_ERR(TAG, "pipe '%s' read returned %s [%d]", pipe,
1669
0
               winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
1670
0
      break;
1671
0
    }
1672
0
    else
1673
0
    {
1674
0
      if (!common_inject_keypress(context, buffer, WINPR_ASSERTING_INT_CAST(size_t, rd)))
1675
0
        break;
1676
0
    }
1677
0
  }
1678
0
  close(fd);
1679
0
  return TRUE;
1680
0
}
1681
1682
static void common_cleanup_pipe(WINPR_ATTR_UNUSED int signum, WINPR_ATTR_UNUSED const char* signame,
1683
                                void* context)
1684
0
{
1685
0
  const char* pipe = context;
1686
0
  if (!pipe)
1687
0
    return;
1688
0
  unlink(pipe);
1689
0
}
1690
1691
static DWORD WINAPI common_handle_pipe(void* arg)
1692
0
{
1693
0
  rdpClientContext* cctx = arg;
1694
0
  WINPR_ASSERT(cctx);
1695
1696
0
  rdpContext* context = &cctx->context;
1697
0
  WINPR_ASSERT(context);
1698
1699
0
  rdpSettings* settings = context->settings;
1700
0
  WINPR_ASSERT(settings);
1701
1702
0
  const char* pipe = freerdp_settings_get_string(settings, FreeRDP_KeyboardPipeName);
1703
0
  WINPR_ASSERT(pipe);
1704
1705
0
  const int rc = mkfifo(pipe, S_IWUSR | S_IRUSR);
1706
0
  if (rc != 0)
1707
0
  {
1708
0
    char ebuffer[256] = WINPR_C_ARRAY_INIT;
1709
0
    WLog_ERR(TAG, "Failed to create named pipe '%s': %s [%d]", pipe,
1710
0
             winpr_strerror(errno, ebuffer, sizeof(ebuffer)), errno);
1711
0
    return 0;
1712
0
  }
1713
1714
0
  void* ctx = WINPR_CAST_CONST_PTR_AWAY(pipe, void*);
1715
0
  if (freerdp_add_signal_cleanup_handler(ctx, common_cleanup_pipe))
1716
0
  {
1717
1718
0
    common_process_pipe(context, pipe);
1719
1720
0
    freerdp_del_signal_cleanup_handler(ctx, common_cleanup_pipe);
1721
0
  }
1722
1723
0
  unlink(pipe);
1724
1725
0
  return 0;
1726
0
}
1727
#endif
1728
1729
static BOOL commonPreConnect(freerdp* instance)
1730
0
{
1731
0
  WINPR_ASSERT(instance);
1732
0
  WINPR_ASSERT(requireRdpClientContext(instance->context));
1733
1734
0
  rdpClientContext* cctx = (rdpClientContext*)instance->context;
1735
1736
0
  return IFCALLRESULT(TRUE, cctx->PreConnect, instance);
1737
0
}
1738
1739
static BOOL commonPostConnect(freerdp* instance)
1740
0
{
1741
0
  WINPR_ASSERT(instance);
1742
0
  WINPR_ASSERT(requireRdpClientContext(instance->context));
1743
1744
0
  rdpClientContext* cctx = (rdpClientContext*)instance->context;
1745
1746
0
  if (!IFCALLRESULT(TRUE, cctx->PostConnect, instance))
1747
0
    return FALSE;
1748
1749
0
  const char* pipe =
1750
0
      freerdp_settings_get_string(cctx->context.settings, FreeRDP_KeyboardPipeName);
1751
0
  if (pipe)
1752
0
  {
1753
0
#if !defined(_WIN32)
1754
0
    cctx->pipethread = CreateThread(nullptr, 0, common_handle_pipe, cctx, 0, nullptr);
1755
0
    if (!cctx->pipethread)
1756
0
      return FALSE;
1757
#else
1758
    WLog_WARN(TAG, "/kbd:pipe:<filename> not supported on windows");
1759
#endif
1760
0
  }
1761
1762
0
  return TRUE;
1763
0
}
1764
1765
static void commonPostDisconnect(freerdp* instance)
1766
0
{
1767
0
  WINPR_ASSERT(instance);
1768
0
  WINPR_ASSERT(requireRdpClientContext(instance->context));
1769
1770
0
  rdpClientContext* cctx = (rdpClientContext*)instance->context;
1771
1772
0
#if !defined(_WIN32)
1773
0
  if (cctx->pipethread)
1774
0
  {
1775
0
    (void)WaitForSingleObject(cctx->pipethread, INFINITE);
1776
0
    (void)CloseHandle(cctx->pipethread);
1777
0
    cctx->pipethread = nullptr;
1778
0
  }
1779
0
#endif
1780
1781
0
  IFCALL(cctx->PostDisconnect, instance);
1782
1783
  // TODO
1784
0
}
1785
1786
static void commonPostFinalDisconnect(freerdp* instance)
1787
0
{
1788
0
  WINPR_ASSERT(instance);
1789
0
  WINPR_ASSERT(requireRdpClientContext(instance->context));
1790
1791
0
  rdpClientContext* cctx = (rdpClientContext*)instance->context;
1792
1793
0
  IFCALL(cctx->PostFinalDisconnect, instance);
1794
1795
  // TODO
1796
0
}
1797
1798
BOOL freerdp_client_common_pre_connect(rdpContext* context)
1799
2.06k
{
1800
2.06k
  WINPR_ASSERT(context);
1801
1802
2.06k
  if (requireRdpClientContext(context))
1803
0
  {
1804
0
    rdpClientContext* cctx = (rdpClientContext*)context;
1805
0
    freerdp* instance = context->instance;
1806
1807
0
    cctx->PreConnect = instance->PreConnect;
1808
0
    cctx->PostConnect = instance->PostConnect;
1809
0
    cctx->PostDisconnect = instance->PostDisconnect;
1810
0
    cctx->PostFinalDisconnect = instance->PostFinalDisconnect;
1811
1812
0
    instance->PreConnect = commonPreConnect;
1813
0
    instance->PostConnect = commonPostConnect;
1814
0
    instance->PostDisconnect = commonPostDisconnect;
1815
0
    instance->PostFinalDisconnect = commonPostFinalDisconnect;
1816
0
  }
1817
2.06k
  return TRUE;
1818
2.06k
}
1819
1820
int freerdp_client_common_stop(rdpContext* context)
1821
0
{
1822
0
  freerdp_abort_connect_context(context);
1823
1824
0
  if (requireRdpClientContext(context))
1825
0
  {
1826
1827
0
    rdpClientContext* cctx = (rdpClientContext*)context;
1828
0
    WINPR_ASSERT(cctx);
1829
0
    if (cctx->thread)
1830
0
    {
1831
0
      (void)WaitForSingleObject(cctx->thread, INFINITE);
1832
0
      (void)CloseHandle(cctx->thread);
1833
0
      cctx->thread = nullptr;
1834
0
    }
1835
#if defined(WITH_AAD)
1836
    aad_auth_helper_stop(cctx->aadHelper);
1837
    cctx->aadHelper = nullptr;
1838
#endif
1839
0
  }
1840
1841
0
  return 0;
1842
0
}
1843
1844
#if defined(CHANNEL_ENCOMSP_CLIENT)
1845
BOOL freerdp_client_encomsp_toggle_control(EncomspClientContext* encomsp)
1846
0
{
1847
0
  BOOL state = 0;
1848
1849
0
  if (!encomsp)
1850
0
    return FALSE;
1851
1852
0
  rdpClientContext* cctx = (rdpClientContext*)encomsp->custom;
1853
0
  WINPR_ASSERT(cctx);
1854
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
1855
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
1856
0
    return FALSE;
1857
1858
0
  state = cctx->controlToggle;
1859
0
  cctx->controlToggle = !cctx->controlToggle;
1860
0
  return freerdp_client_encomsp_set_control(encomsp, state);
1861
0
}
1862
1863
BOOL freerdp_client_encomsp_set_control(EncomspClientContext* encomsp, BOOL control)
1864
0
{
1865
0
  ENCOMSP_CHANGE_PARTICIPANT_CONTROL_LEVEL_PDU pdu = WINPR_C_ARRAY_INIT;
1866
1867
0
  if (!encomsp)
1868
0
    return FALSE;
1869
1870
0
  pdu.ParticipantId = encomsp->participantId;
1871
0
  pdu.Flags = ENCOMSP_REQUEST_VIEW;
1872
1873
0
  if (control)
1874
0
    pdu.Flags |= ENCOMSP_REQUEST_INTERACT;
1875
1876
0
  const UINT rc = encomsp->ChangeParticipantControlLevel(encomsp, &pdu);
1877
0
  return rc == CHANNEL_RC_OK;
1878
0
}
1879
1880
static UINT
1881
client_encomsp_participant_created(EncomspClientContext* context,
1882
                                   const ENCOMSP_PARTICIPANT_CREATED_PDU* participantCreated)
1883
0
{
1884
0
  BOOL request = 0;
1885
1886
0
  if (!context || !context->custom || !participantCreated)
1887
0
    return ERROR_INVALID_PARAMETER;
1888
1889
0
  rdpClientContext* cctx = (rdpClientContext*)context->custom;
1890
0
  WINPR_ASSERT(cctx);
1891
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
1892
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
1893
0
    return ERROR_INVALID_PARAMETER;
1894
1895
0
  rdpSettings* settings = cctx->context.settings;
1896
0
  WINPR_ASSERT(settings);
1897
1898
0
  if (participantCreated->Flags & ENCOMSP_IS_PARTICIPANT)
1899
0
    context->participantId = participantCreated->ParticipantId;
1900
1901
0
  request = freerdp_settings_get_bool(settings, FreeRDP_RemoteAssistanceRequestControl);
1902
0
  if (request && (participantCreated->Flags & ENCOMSP_MAY_VIEW) &&
1903
0
      !(participantCreated->Flags & ENCOMSP_MAY_INTERACT))
1904
0
  {
1905
0
    if (!freerdp_client_encomsp_set_control(context, TRUE))
1906
0
      return ERROR_INTERNAL_ERROR;
1907
1908
    /* if auto-request-control setting is enabled then only request control once upon connect,
1909
     * otherwise it will auto request control again every time server turns off control which
1910
     * is a bit annoying */
1911
0
    if (!freerdp_settings_set_bool(settings, FreeRDP_RemoteAssistanceRequestControl, FALSE))
1912
0
      return ERROR_INTERNAL_ERROR;
1913
0
  }
1914
1915
0
  return CHANNEL_RC_OK;
1916
0
}
1917
1918
static void client_encomsp_init(rdpClientContext* cctx, EncomspClientContext* encomsp)
1919
0
{
1920
0
  WINPR_ASSERT(cctx);
1921
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
1922
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
1923
0
    return;
1924
1925
0
  cctx->encomsp = encomsp;
1926
0
  encomsp->custom = (void*)cctx;
1927
0
  encomsp->ParticipantCreated = client_encomsp_participant_created;
1928
0
}
1929
1930
static void client_encomsp_uninit(rdpClientContext* cctx, EncomspClientContext* encomsp)
1931
0
{
1932
0
  if (encomsp)
1933
0
  {
1934
0
    encomsp->custom = nullptr;
1935
0
    encomsp->ParticipantCreated = nullptr;
1936
0
  }
1937
1938
0
  if (cctx)
1939
0
  {
1940
0
    WINPR_ASSERT(requireRdpClientContext(&cctx->context));
1941
0
    if (requireRdpClientContext(&cctx->context))
1942
0
      cctx->encomsp = nullptr;
1943
0
  }
1944
0
}
1945
#endif
1946
1947
void freerdp_client_OnChannelConnectedEventHandler(void* context,
1948
                                                   const ChannelConnectedEventArgs* e)
1949
0
{
1950
0
  rdpClientContext* cctx = (rdpClientContext*)context;
1951
1952
0
  WINPR_ASSERT(cctx);
1953
0
  WINPR_ASSERT(e);
1954
1955
0
  if (0)
1956
0
  {
1957
0
  }
1958
0
#if defined(CHANNEL_AINPUT_CLIENT)
1959
0
  else if (strcmp(e->name, AINPUT_DVC_CHANNEL_NAME) == 0)
1960
0
  {
1961
1962
0
    WINPR_ASSERT(requireRdpClientContext(context));
1963
0
    if (!requireRdpClientContext(context))
1964
0
      return;
1965
0
    cctx->ainput = (AInputClientContext*)e->pInterface;
1966
0
  }
1967
0
#endif
1968
0
#if defined(CHANNEL_RDPEI_CLIENT)
1969
0
  else if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0)
1970
0
  {
1971
0
    WINPR_ASSERT(requireRdpClientContext(context));
1972
0
    if (!requireRdpClientContext(context))
1973
0
      return;
1974
0
    cctx->rdpei = (RdpeiClientContext*)e->pInterface;
1975
0
  }
1976
0
#endif
1977
0
#if defined(CHANNEL_RDPGFX_CLIENT)
1978
0
  else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
1979
0
  {
1980
0
    gdi_graphics_pipeline_init(cctx->context.gdi, (RdpgfxClientContext*)e->pInterface);
1981
0
  }
1982
0
#endif
1983
0
#if defined(CHANNEL_GEOMETRY_CLIENT)
1984
0
  else if (strcmp(e->name, GEOMETRY_DVC_CHANNEL_NAME) == 0)
1985
0
  {
1986
0
    gdi_video_geometry_init(cctx->context.gdi, (GeometryClientContext*)e->pInterface);
1987
0
  }
1988
0
#endif
1989
0
#if defined(CHANNEL_VIDEO_CLIENT)
1990
0
  else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0)
1991
0
  {
1992
0
    gdi_video_control_init(cctx->context.gdi, (VideoClientContext*)e->pInterface);
1993
0
  }
1994
0
  else if (strcmp(e->name, VIDEO_DATA_DVC_CHANNEL_NAME) == 0)
1995
0
  {
1996
0
    gdi_video_data_init(cctx->context.gdi, (VideoClientContext*)e->pInterface);
1997
0
  }
1998
0
#endif
1999
0
#if defined(CHANNEL_ENCOMSP_CLIENT)
2000
0
  else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0)
2001
0
  {
2002
0
    client_encomsp_init(cctx, (EncomspClientContext*)e->pInterface);
2003
0
  }
2004
0
#endif
2005
0
}
2006
2007
void freerdp_client_OnChannelDisconnectedEventHandler(void* context,
2008
                                                      const ChannelDisconnectedEventArgs* e)
2009
0
{
2010
0
  rdpClientContext* cctx = (rdpClientContext*)context;
2011
2012
0
  WINPR_ASSERT(cctx);
2013
0
  WINPR_ASSERT(e);
2014
2015
0
  if (0)
2016
0
  {
2017
0
  }
2018
0
#if defined(CHANNEL_AINPUT_CLIENT)
2019
0
  else if (strcmp(e->name, AINPUT_DVC_CHANNEL_NAME) == 0)
2020
0
  {
2021
0
    WINPR_ASSERT(requireRdpClientContext(context));
2022
0
    if (!requireRdpClientContext(context))
2023
0
      return;
2024
0
    cctx->ainput = nullptr;
2025
0
  }
2026
0
#endif
2027
0
#if defined(CHANNEL_RDPEI_CLIENT)
2028
0
  else if (strcmp(e->name, RDPEI_DVC_CHANNEL_NAME) == 0)
2029
0
  {
2030
0
    WINPR_ASSERT(requireRdpClientContext(context));
2031
0
    if (!requireRdpClientContext(context))
2032
0
      return;
2033
0
    cctx->rdpei = nullptr;
2034
0
  }
2035
0
#endif
2036
0
#if defined(CHANNEL_RDPGFX_CLIENT)
2037
0
  else if (strcmp(e->name, RDPGFX_DVC_CHANNEL_NAME) == 0)
2038
0
  {
2039
0
    gdi_graphics_pipeline_uninit(cctx->context.gdi, (RdpgfxClientContext*)e->pInterface);
2040
0
  }
2041
0
#endif
2042
0
#if defined(CHANNEL_GEOMETRY_CLIENT)
2043
0
  else if (strcmp(e->name, GEOMETRY_DVC_CHANNEL_NAME) == 0)
2044
0
  {
2045
0
    gdi_video_geometry_uninit(cctx->context.gdi, (GeometryClientContext*)e->pInterface);
2046
0
  }
2047
0
#endif
2048
0
#if defined(CHANNEL_VIDEO_CLIENT)
2049
0
  else if (strcmp(e->name, VIDEO_CONTROL_DVC_CHANNEL_NAME) == 0)
2050
0
  {
2051
0
    gdi_video_control_uninit(cctx->context.gdi, (VideoClientContext*)e->pInterface);
2052
0
  }
2053
0
  else if (strcmp(e->name, VIDEO_DATA_DVC_CHANNEL_NAME) == 0)
2054
0
  {
2055
0
    gdi_video_data_uninit(cctx->context.gdi, (VideoClientContext*)e->pInterface);
2056
0
  }
2057
0
#endif
2058
0
#if defined(CHANNEL_ENCOMSP_CLIENT)
2059
0
  else if (strcmp(e->name, ENCOMSP_SVC_CHANNEL_NAME) == 0)
2060
0
  {
2061
0
    client_encomsp_uninit(cctx, (EncomspClientContext*)e->pInterface);
2062
0
  }
2063
0
#endif
2064
0
}
2065
2066
BOOL freerdp_client_send_wheel_event(rdpClientContext* cctx, UINT16 mflags)
2067
0
{
2068
0
  BOOL handled = FALSE;
2069
2070
0
  WINPR_ASSERT(cctx);
2071
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2072
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2073
0
    return FALSE;
2074
2075
0
  const CONNECTION_STATE state = freerdp_get_state(&cctx->context);
2076
0
  if (state != CONNECTION_STATE_ACTIVE)
2077
0
    return TRUE;
2078
2079
0
#if defined(CHANNEL_AINPUT_CLIENT)
2080
0
  if (cctx->ainput)
2081
0
  {
2082
0
    UINT rc = 0;
2083
0
    UINT64 flags = 0;
2084
0
    INT32 x = 0;
2085
0
    INT32 y = 0;
2086
0
    INT32 value = mflags & 0xFF;
2087
2088
0
    if (mflags & PTR_FLAGS_WHEEL_NEGATIVE)
2089
0
      value = -1 * (0x100 - value);
2090
2091
    /* We have discrete steps, scale this so we can also support high
2092
     * resolution wheels. */
2093
0
    value *= 0x10000;
2094
2095
0
    if (mflags & PTR_FLAGS_WHEEL)
2096
0
    {
2097
0
      flags |= AINPUT_FLAGS_WHEEL;
2098
0
      y = value;
2099
0
    }
2100
2101
0
    if (mflags & PTR_FLAGS_HWHEEL)
2102
0
    {
2103
0
      flags |= AINPUT_FLAGS_WHEEL;
2104
0
      x = value;
2105
0
    }
2106
2107
0
    WINPR_ASSERT(cctx->ainput->AInputSendInputEvent);
2108
0
    rc = cctx->ainput->AInputSendInputEvent(cctx->ainput, flags, x, y);
2109
0
    if (rc == CHANNEL_RC_OK)
2110
0
      handled = TRUE;
2111
0
  }
2112
0
#endif
2113
2114
0
  if (!handled)
2115
0
    return freerdp_input_send_mouse_event(cctx->context.input, mflags, 0, 0);
2116
2117
0
  return TRUE;
2118
0
}
2119
2120
#if defined(CHANNEL_AINPUT_CLIENT)
2121
WINPR_ATTR_NODISCARD
2122
static inline BOOL ainput_send_diff_event(rdpClientContext* cctx, UINT64 flags, INT32 x, INT32 y)
2123
0
{
2124
0
  WINPR_ASSERT(cctx);
2125
0
  WINPR_ASSERT(cctx->ainput);
2126
0
  WINPR_ASSERT(cctx->ainput->AInputSendInputEvent);
2127
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2128
2129
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2130
0
    return ERROR_INVALID_PARAMETER;
2131
2132
0
  const UINT rc = cctx->ainput->AInputSendInputEvent(cctx->ainput, flags, x, y);
2133
2134
0
  return rc == CHANNEL_RC_OK;
2135
0
}
2136
#endif
2137
2138
WINPR_ATTR_NODISCARD
2139
static bool button_pressed(const rdpClientContext* cctx)
2140
0
{
2141
0
  WINPR_ASSERT(cctx);
2142
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2143
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2144
0
    return false;
2145
2146
0
  for (size_t x = 0; x < ARRAYSIZE(cctx->pressed_buttons); x++)
2147
0
  {
2148
0
    const BOOL cur = cctx->pressed_buttons[x];
2149
0
    if (cur)
2150
0
      return true;
2151
0
  }
2152
0
  return false;
2153
0
}
2154
2155
BOOL freerdp_client_send_button_event(rdpClientContext* cctx, BOOL relative, UINT16 mflags, INT32 x,
2156
                                      INT32 y)
2157
0
{
2158
0
  BOOL handled = FALSE;
2159
2160
0
  WINPR_ASSERT(cctx);
2161
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2162
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2163
0
    return FALSE;
2164
2165
0
  const CONNECTION_STATE state = freerdp_get_state(&cctx->context);
2166
0
  if (state != CONNECTION_STATE_ACTIVE)
2167
0
    return TRUE;
2168
2169
0
  if (mflags & PTR_FLAGS_BUTTON1)
2170
0
    cctx->pressed_buttons[0] = mflags & PTR_FLAGS_DOWN;
2171
0
  if (mflags & PTR_FLAGS_BUTTON2)
2172
0
    cctx->pressed_buttons[1] = mflags & PTR_FLAGS_DOWN;
2173
0
  if (mflags & PTR_FLAGS_BUTTON3)
2174
0
    cctx->pressed_buttons[2] = mflags & PTR_FLAGS_DOWN;
2175
2176
0
  if (((mflags & PTR_FLAGS_MOVE) != 0) &&
2177
0
      !freerdp_settings_get_bool(cctx->context.settings, FreeRDP_MouseMotion))
2178
0
  {
2179
0
    if (!button_pressed(cctx))
2180
0
      return TRUE;
2181
0
  }
2182
2183
0
  const BOOL haveRelative =
2184
0
      freerdp_settings_get_bool(cctx->context.settings, FreeRDP_HasRelativeMouseEvent);
2185
0
  if (relative && haveRelative)
2186
0
  {
2187
0
    return freerdp_input_send_rel_mouse_event(cctx->context.input, mflags,
2188
0
                                              WINPR_ASSERTING_INT_CAST(int16_t, x),
2189
0
                                              WINPR_ASSERTING_INT_CAST(int16_t, y));
2190
0
  }
2191
2192
0
#if defined(CHANNEL_AINPUT_CLIENT)
2193
0
  if (cctx->ainput)
2194
0
  {
2195
0
    UINT64 flags = 0;
2196
2197
0
    if (cctx->mouse_grabbed && freerdp_client_use_relative_mouse_events(cctx))
2198
0
      flags |= AINPUT_FLAGS_HAVE_REL;
2199
2200
0
    if (relative)
2201
0
      flags |= AINPUT_FLAGS_REL;
2202
2203
0
    if (mflags & PTR_FLAGS_DOWN)
2204
0
      flags |= AINPUT_FLAGS_DOWN;
2205
0
    if (mflags & PTR_FLAGS_BUTTON1)
2206
0
      flags |= AINPUT_FLAGS_BUTTON1;
2207
0
    if (mflags & PTR_FLAGS_BUTTON2)
2208
0
      flags |= AINPUT_FLAGS_BUTTON2;
2209
0
    if (mflags & PTR_FLAGS_BUTTON3)
2210
0
      flags |= AINPUT_FLAGS_BUTTON3;
2211
0
    if (mflags & PTR_FLAGS_MOVE)
2212
0
      flags |= AINPUT_FLAGS_MOVE;
2213
0
    handled = ainput_send_diff_event(cctx, flags, x, y);
2214
0
  }
2215
0
#endif
2216
2217
0
  if (!handled)
2218
0
  {
2219
0
    if (relative)
2220
0
    {
2221
0
      cctx->lastX += x;
2222
0
      cctx->lastY += y;
2223
0
      WLog_WARN(TAG, "Relative mouse input channel not available, sending absolute!");
2224
0
    }
2225
0
    else
2226
0
    {
2227
0
      cctx->lastX = x;
2228
0
      cctx->lastY = y;
2229
0
    }
2230
0
    return freerdp_input_send_mouse_event(cctx->context.input, mflags, (UINT16)cctx->lastX,
2231
0
                                          (UINT16)cctx->lastY);
2232
0
  }
2233
0
  return TRUE;
2234
0
}
2235
2236
BOOL freerdp_client_send_extended_button_event(rdpClientContext* cctx, BOOL relative, UINT16 mflags,
2237
                                               INT32 x, INT32 y)
2238
0
{
2239
0
  BOOL handled = FALSE;
2240
0
  WINPR_ASSERT(cctx);
2241
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2242
2243
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2244
0
    return FALSE;
2245
2246
0
  const CONNECTION_STATE state = freerdp_get_state(&cctx->context);
2247
0
  if (state != CONNECTION_STATE_ACTIVE)
2248
0
    return TRUE;
2249
2250
0
  if (mflags & PTR_XFLAGS_BUTTON1)
2251
0
    cctx->pressed_buttons[3] = mflags & PTR_XFLAGS_DOWN;
2252
0
  if (mflags & PTR_XFLAGS_BUTTON2)
2253
0
    cctx->pressed_buttons[4] = mflags & PTR_XFLAGS_DOWN;
2254
2255
0
  const BOOL haveRelative =
2256
0
      freerdp_settings_get_bool(cctx->context.settings, FreeRDP_HasRelativeMouseEvent);
2257
0
  if (relative && haveRelative)
2258
0
  {
2259
0
    return freerdp_input_send_rel_mouse_event(cctx->context.input, mflags,
2260
0
                                              WINPR_ASSERTING_INT_CAST(int16_t, x),
2261
0
                                              WINPR_ASSERTING_INT_CAST(int16_t, y));
2262
0
  }
2263
2264
0
#if defined(CHANNEL_AINPUT_CLIENT)
2265
0
  if (cctx->ainput)
2266
0
  {
2267
0
    UINT64 flags = 0;
2268
2269
0
    if (relative)
2270
0
      flags |= AINPUT_FLAGS_REL;
2271
0
    if (mflags & PTR_XFLAGS_DOWN)
2272
0
      flags |= AINPUT_FLAGS_DOWN;
2273
0
    if (mflags & PTR_XFLAGS_BUTTON1)
2274
0
      flags |= AINPUT_XFLAGS_BUTTON1;
2275
0
    if (mflags & PTR_XFLAGS_BUTTON2)
2276
0
      flags |= AINPUT_XFLAGS_BUTTON2;
2277
2278
0
    handled = ainput_send_diff_event(cctx, flags, x, y);
2279
0
  }
2280
0
#endif
2281
2282
0
  if (!handled)
2283
0
  {
2284
0
    if (relative)
2285
0
    {
2286
0
      cctx->lastX += x;
2287
0
      cctx->lastY += y;
2288
0
      WLog_WARN(TAG, "Relative mouse input channel not available, sending absolute!");
2289
0
    }
2290
0
    else
2291
0
    {
2292
0
      cctx->lastX = x;
2293
0
      cctx->lastY = y;
2294
0
    }
2295
0
    freerdp_input_send_extended_mouse_event(cctx->context.input, mflags, (UINT16)cctx->lastX,
2296
0
                                            (UINT16)cctx->lastY);
2297
0
  }
2298
2299
0
  return TRUE;
2300
0
}
2301
2302
WINPR_ATTR_NODISCARD
2303
static BOOL freerdp_handle_touch_to_mouse(rdpClientContext* cctx, BOOL down,
2304
                                          const FreeRDP_TouchContact* contact)
2305
0
{
2306
0
  WINPR_ASSERT(cctx);
2307
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2308
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2309
0
    return FALSE;
2310
2311
0
  const UINT16 flags = PTR_FLAGS_MOVE | (down ? PTR_FLAGS_DOWN : 0);
2312
0
  const UINT16 xflags = down ? PTR_XFLAGS_DOWN : 0;
2313
0
  WINPR_ASSERT(contact);
2314
0
  WINPR_ASSERT(contact->x <= UINT16_MAX);
2315
0
  WINPR_ASSERT(contact->y <= UINT16_MAX);
2316
2317
0
  switch (contact->count)
2318
0
  {
2319
0
    case 1:
2320
0
      return freerdp_client_send_button_event(cctx, FALSE, flags | PTR_FLAGS_BUTTON1,
2321
0
                                              contact->x, contact->y);
2322
0
    case 2:
2323
0
      return freerdp_client_send_button_event(cctx, FALSE, flags | PTR_FLAGS_BUTTON2,
2324
0
                                              contact->x, contact->y);
2325
0
    case 3:
2326
0
      return freerdp_client_send_button_event(cctx, FALSE, flags | PTR_FLAGS_BUTTON3,
2327
0
                                              contact->x, contact->y);
2328
0
    case 4:
2329
0
      return freerdp_client_send_extended_button_event(
2330
0
          cctx, FALSE, xflags | PTR_XFLAGS_BUTTON1, contact->x, contact->y);
2331
0
    case 5:
2332
0
      return freerdp_client_send_extended_button_event(
2333
0
          cctx, FALSE, xflags | PTR_XFLAGS_BUTTON1, contact->x, contact->y);
2334
0
    default:
2335
      /* unmapped events, ignore */
2336
0
      return TRUE;
2337
0
  }
2338
0
}
2339
2340
WINPR_ATTR_NODISCARD
2341
static BOOL freerdp_handle_touch_up(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
2342
0
{
2343
0
  WINPR_ASSERT(cctx);
2344
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2345
0
  WINPR_ASSERT(contact);
2346
2347
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2348
0
    return FALSE;
2349
2350
0
#if defined(CHANNEL_RDPEI_CLIENT)
2351
0
  RdpeiClientContext* rdpei = cctx->rdpei;
2352
2353
0
  if (!rdpei)
2354
0
    return freerdp_handle_touch_to_mouse(cctx, FALSE, contact);
2355
2356
0
  int contactId = 0;
2357
2358
0
  if (rdpei->TouchRawEvent)
2359
0
  {
2360
0
    const UINT32 flags = RDPINPUT_CONTACT_FLAG_UP;
2361
0
    const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
2362
0
                                    ? CONTACT_DATA_PRESSURE_PRESENT
2363
0
                                    : 0;
2364
    // Ensure contact position is unchanged from "engaged" to "out of range" state
2365
0
    const UINT rc1 =
2366
0
        rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId,
2367
0
                             RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
2368
0
                                 RDPINPUT_CONTACT_FLAG_INCONTACT,
2369
0
                             contactFlags, contact->pressure);
2370
0
    if (rc1 != CHANNEL_RC_OK)
2371
0
      return FALSE;
2372
2373
0
    const UINT rc2 = rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y,
2374
0
                                          &contactId, flags, contactFlags, contact->pressure);
2375
0
    if (rc2 != CHANNEL_RC_OK)
2376
0
      return FALSE;
2377
0
  }
2378
0
  else
2379
0
  {
2380
0
    WINPR_ASSERT(rdpei->TouchEnd);
2381
0
    const UINT rc = rdpei->TouchEnd(rdpei, contact->id, contact->x, contact->y, &contactId);
2382
0
    if (rc != CHANNEL_RC_OK)
2383
0
      return FALSE;
2384
0
  }
2385
0
  return TRUE;
2386
#else
2387
  WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
2388
                 "-DCHANNEL_RDPEI_CLIENT=ON");
2389
  return freerdp_handle_touch_to_mouse(cctx, FALSE, contact);
2390
#endif
2391
0
}
2392
2393
WINPR_ATTR_NODISCARD
2394
static BOOL freerdp_handle_touch_down(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
2395
0
{
2396
0
  WINPR_ASSERT(cctx);
2397
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2398
0
  WINPR_ASSERT(contact);
2399
2400
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2401
0
    return FALSE;
2402
2403
0
#if defined(CHANNEL_RDPEI_CLIENT)
2404
0
  RdpeiClientContext* rdpei = cctx->rdpei;
2405
2406
  // Emulate mouse click if touch is not possible, like in login screen
2407
0
  if (!rdpei)
2408
0
    return freerdp_handle_touch_to_mouse(cctx, TRUE, contact);
2409
2410
0
  int contactId = 0;
2411
2412
0
  if (rdpei->TouchRawEvent)
2413
0
  {
2414
0
    const UINT32 flags = RDPINPUT_CONTACT_FLAG_DOWN | RDPINPUT_CONTACT_FLAG_INRANGE |
2415
0
                         RDPINPUT_CONTACT_FLAG_INCONTACT;
2416
0
    const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
2417
0
                                    ? CONTACT_DATA_PRESSURE_PRESENT
2418
0
                                    : 0;
2419
0
    const UINT rc = rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId,
2420
0
                                         flags, contactFlags, contact->pressure);
2421
0
    if (rc != CHANNEL_RC_OK)
2422
0
      return FALSE;
2423
0
  }
2424
0
  else
2425
0
  {
2426
0
    WINPR_ASSERT(rdpei->TouchBegin);
2427
0
    const UINT rc = rdpei->TouchBegin(rdpei, contact->id, contact->x, contact->y, &contactId);
2428
0
    if (rc != CHANNEL_RC_OK)
2429
0
      return FALSE;
2430
0
  }
2431
2432
0
  return TRUE;
2433
#else
2434
  WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
2435
                 "-DCHANNEL_RDPEI_CLIENT=ON");
2436
  return freerdp_handle_touch_to_mouse(cctx, TRUE, contact);
2437
#endif
2438
0
}
2439
2440
WINPR_ATTR_NODISCARD
2441
static BOOL freerdp_handle_touch_motion_to_mouse(rdpClientContext* cctx,
2442
                                                 const FreeRDP_TouchContact* contact)
2443
0
{
2444
0
  WINPR_ASSERT(cctx);
2445
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2446
2447
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2448
0
    return FALSE;
2449
2450
0
  const UINT16 flags = PTR_FLAGS_MOVE;
2451
2452
0
  WINPR_ASSERT(contact);
2453
0
  WINPR_ASSERT(contact->x <= UINT16_MAX);
2454
0
  WINPR_ASSERT(contact->y <= UINT16_MAX);
2455
0
  return freerdp_client_send_button_event(cctx, FALSE, flags, contact->x, contact->y);
2456
0
}
2457
2458
WINPR_ATTR_NODISCARD
2459
static BOOL freerdp_handle_touch_motion(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
2460
0
{
2461
0
  WINPR_ASSERT(cctx);
2462
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2463
0
  WINPR_ASSERT(contact);
2464
2465
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2466
0
    return FALSE;
2467
2468
0
#if defined(CHANNEL_RDPEI_CLIENT)
2469
0
  RdpeiClientContext* rdpei = cctx->rdpei;
2470
2471
0
  if (!rdpei)
2472
0
    return freerdp_handle_touch_motion_to_mouse(cctx, contact);
2473
2474
0
  int contactId = 0;
2475
2476
0
  if (rdpei->TouchRawEvent)
2477
0
  {
2478
0
    const UINT32 flags = RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_INRANGE |
2479
0
                         RDPINPUT_CONTACT_FLAG_INCONTACT;
2480
0
    const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
2481
0
                                    ? CONTACT_DATA_PRESSURE_PRESENT
2482
0
                                    : 0;
2483
0
    const UINT rc = rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId,
2484
0
                                         flags, contactFlags, contact->pressure);
2485
0
    if (rc != CHANNEL_RC_OK)
2486
0
      return FALSE;
2487
0
  }
2488
0
  else
2489
0
  {
2490
0
    WINPR_ASSERT(rdpei->TouchUpdate);
2491
0
    const UINT rc = rdpei->TouchUpdate(rdpei, contact->id, contact->x, contact->y, &contactId);
2492
0
    if (rc != CHANNEL_RC_OK)
2493
0
      return FALSE;
2494
0
  }
2495
2496
0
  return TRUE;
2497
#else
2498
  WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
2499
                 "-DCHANNEL_RDPEI_CLIENT=ON");
2500
  return freerdp_handle_touch_motion_to_mouse(cctx, contact);
2501
#endif
2502
0
}
2503
2504
WINPR_ATTR_NODISCARD
2505
static BOOL freerdp_handle_touch_cancel(rdpClientContext* cctx, const FreeRDP_TouchContact* contact)
2506
0
{
2507
0
  WINPR_ASSERT(cctx);
2508
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2509
0
  WINPR_ASSERT(contact);
2510
2511
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2512
0
    return FALSE;
2513
2514
0
#if defined(CHANNEL_RDPEI_CLIENT)
2515
0
  RdpeiClientContext* rdpei = cctx->rdpei;
2516
2517
0
  if (!rdpei)
2518
0
    return freerdp_handle_touch_to_mouse(cctx, false, contact);
2519
2520
0
  int contactId = 0;
2521
2522
0
  if (rdpei->TouchRawEvent)
2523
0
  {
2524
0
    const UINT32 flags = RDPINPUT_CONTACT_FLAG_UPDATE | RDPINPUT_CONTACT_FLAG_CANCELED;
2525
0
    const UINT32 contactFlags = ((contact->flags & FREERDP_TOUCH_HAS_PRESSURE) != 0)
2526
0
                                    ? CONTACT_DATA_PRESSURE_PRESENT
2527
0
                                    : 0;
2528
0
    const UINT rc = rdpei->TouchRawEvent(rdpei, contact->id, contact->x, contact->y, &contactId,
2529
0
                                         flags, contactFlags, contact->pressure);
2530
0
    if (rc != CHANNEL_RC_OK)
2531
0
      return FALSE;
2532
0
  }
2533
0
  else
2534
0
  {
2535
0
    WINPR_ASSERT(rdpei->TouchUpdate);
2536
0
    const UINT rc = rdpei->TouchEnd(rdpei, contact->id, contact->x, contact->y, &contactId);
2537
0
    if (rc != CHANNEL_RC_OK)
2538
0
      return FALSE;
2539
0
  }
2540
2541
0
  return TRUE;
2542
#else
2543
  WLog_WARN(TAG, "Touch event detected but RDPEI support not compiled in. Recompile with "
2544
                 "-DCHANNEL_RDPEI_CLIENT=ON");
2545
  return freerdp_handle_touch_to_mouse(cctx, false, contact);
2546
#endif
2547
0
}
2548
2549
WINPR_ATTR_NODISCARD
2550
static BOOL freerdp_client_touch_update(rdpClientContext* cctx, UINT32 flags, INT32 touchId,
2551
                                        UINT32 pressure, INT32 x, INT32 y,
2552
                                        FreeRDP_TouchContact* pcontact)
2553
0
{
2554
0
  WINPR_ASSERT(cctx);
2555
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2556
0
  WINPR_ASSERT(pcontact);
2557
2558
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2559
0
    return FALSE;
2560
2561
0
  for (size_t i = 0; i < ARRAYSIZE(cctx->contacts); i++)
2562
0
  {
2563
0
    FreeRDP_TouchContact* contact = &cctx->contacts[i];
2564
2565
0
    const BOOL newcontact = ((contact->id == 0) && ((flags & FREERDP_TOUCH_DOWN) != 0));
2566
0
    if (newcontact || (contact->id == touchId))
2567
0
    {
2568
0
      contact->id = touchId;
2569
0
      contact->flags = flags;
2570
0
      contact->pressure = pressure;
2571
0
      contact->x = x;
2572
0
      contact->y = y;
2573
2574
0
      *pcontact = *contact;
2575
2576
0
      const BOOL resetcontact = (flags & FREERDP_TOUCH_UP) != 0;
2577
0
      if (resetcontact)
2578
0
      {
2579
0
        FreeRDP_TouchContact empty = WINPR_C_ARRAY_INIT;
2580
0
        *contact = empty;
2581
0
      }
2582
0
      return TRUE;
2583
0
    }
2584
0
  }
2585
2586
0
  return FALSE;
2587
0
}
2588
2589
BOOL freerdp_client_handle_touch(rdpClientContext* cctx, UINT32 flags, INT32 finger,
2590
                                 UINT32 pressure, INT32 x, INT32 y)
2591
0
{
2592
0
  WINPR_ASSERT(cctx);
2593
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2594
2595
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2596
0
    return FALSE;
2597
2598
0
  const UINT32 mask =
2599
0
      FREERDP_TOUCH_DOWN | FREERDP_TOUCH_UP | FREERDP_TOUCH_MOTION | FREERDP_TOUCH_CANCEL;
2600
0
  WINPR_ASSERT(cctx);
2601
2602
0
  const CONNECTION_STATE state = freerdp_get_state(&cctx->context);
2603
0
  if (state != CONNECTION_STATE_ACTIVE)
2604
0
    return TRUE;
2605
2606
0
  FreeRDP_TouchContact contact = WINPR_C_ARRAY_INIT;
2607
2608
0
  if (!freerdp_client_touch_update(cctx, flags, finger, pressure, x, y, &contact))
2609
0
    return FALSE;
2610
2611
0
  switch (flags & mask)
2612
0
  {
2613
0
    case FREERDP_TOUCH_DOWN:
2614
0
      return freerdp_handle_touch_down(cctx, &contact);
2615
0
    case FREERDP_TOUCH_UP:
2616
0
      return freerdp_handle_touch_up(cctx, &contact);
2617
0
    case FREERDP_TOUCH_MOTION:
2618
0
      return freerdp_handle_touch_motion(cctx, &contact);
2619
0
    case FREERDP_TOUCH_CANCEL:
2620
0
      return freerdp_handle_touch_cancel(cctx, &contact);
2621
0
    default:
2622
0
      WLog_WARN(TAG, "Unhandled FreeRDPTouchEventType %" PRIu32 ", ignoring", flags);
2623
0
      return FALSE;
2624
0
  }
2625
0
}
2626
2627
BOOL freerdp_client_load_channels(freerdp* instance)
2628
0
{
2629
0
  WINPR_ASSERT(instance);
2630
0
  WINPR_ASSERT(instance->context);
2631
2632
0
  if (!freerdp_client_load_addins(instance->context->channels, instance->context->settings))
2633
0
  {
2634
0
    WLog_ERR(TAG, "Failed to load addins [%08" PRIx32 "]", GetLastError());
2635
0
    return FALSE;
2636
0
  }
2637
0
  return TRUE;
2638
0
}
2639
2640
int client_cli_logon_error_info(freerdp* instance, UINT32 data, UINT32 type)
2641
0
{
2642
0
  const char* str_data = freerdp_get_logon_error_info_data(data);
2643
0
  const char* str_type = freerdp_get_logon_error_info_type(type);
2644
2645
0
  if (!instance || !instance->context)
2646
0
    return -1;
2647
2648
0
  WLog_INFO(TAG, "Logon Error Info %s [%s]", str_data, str_type);
2649
0
  return 1;
2650
0
}
2651
2652
WINPR_ATTR_NODISCARD
2653
static FreeRDP_PenDevice* freerdp_client_get_pen(rdpClientContext* cctx, INT32 deviceid,
2654
                                                 size_t* pos)
2655
0
{
2656
0
  WINPR_ASSERT(cctx);
2657
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2658
2659
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2660
0
    return nullptr;
2661
2662
0
  for (size_t i = 0; i < ARRAYSIZE(cctx->pens); i++)
2663
0
  {
2664
0
    FreeRDP_PenDevice* pen = &cctx->pens[i];
2665
0
    if (deviceid == pen->deviceid)
2666
0
    {
2667
0
      if (pos)
2668
0
        *pos = i;
2669
0
      return pen;
2670
0
    }
2671
0
  }
2672
0
  return nullptr;
2673
0
}
2674
2675
WINPR_ATTR_NODISCARD
2676
static BOOL freerdp_client_register_pen(rdpClientContext* cctx, UINT32 flags, INT32 deviceid,
2677
                                        double pressure)
2678
0
{
2679
0
  static const INT32 null_deviceid = 0;
2680
2681
0
  WINPR_ASSERT(cctx);
2682
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2683
0
  WINPR_ASSERT((flags & FREERDP_PEN_REGISTER) != 0);
2684
2685
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2686
0
    return FALSE;
2687
2688
0
  if (freerdp_client_is_pen(cctx, deviceid))
2689
0
  {
2690
0
    WLog_WARN(TAG, "trying to double register pen device %" PRId32, deviceid);
2691
0
    return FALSE;
2692
0
  }
2693
2694
0
  size_t pos = 0;
2695
0
  FreeRDP_PenDevice* pen = freerdp_client_get_pen(cctx, null_deviceid, &pos);
2696
0
  if (pen)
2697
0
  {
2698
0
    const FreeRDP_PenDevice empty = WINPR_C_ARRAY_INIT;
2699
0
    *pen = empty;
2700
2701
0
    pen->deviceid = deviceid;
2702
0
    pen->max_pressure = pressure;
2703
0
    pen->flags = flags;
2704
2705
0
    WLog_DBG(TAG, "registered pen at index %" PRIuz, pos);
2706
0
    return TRUE;
2707
0
  }
2708
2709
0
  WLog_WARN(TAG, "No free slot for an additional pen device, skipping");
2710
0
  return TRUE;
2711
0
}
2712
2713
BOOL freerdp_client_handle_pen(rdpClientContext* cctx, UINT32 flags, INT32 deviceid, ...)
2714
0
{
2715
0
  WINPR_ASSERT(cctx);
2716
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2717
2718
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2719
0
    return FALSE;
2720
2721
0
  const CONNECTION_STATE state = freerdp_get_state(&cctx->context);
2722
0
  if (state != CONNECTION_STATE_ACTIVE)
2723
0
    return TRUE;
2724
2725
0
#if defined(CHANNEL_RDPEI_CLIENT)
2726
0
  if ((flags & FREERDP_PEN_REGISTER) != 0)
2727
0
  {
2728
0
    va_list args = WINPR_C_ARRAY_INIT;
2729
2730
0
    va_start(args, deviceid);
2731
0
    double pressure = va_arg(args, double);
2732
0
    va_end(args);
2733
0
    return freerdp_client_register_pen(cctx, flags, deviceid, pressure);
2734
0
  }
2735
0
  size_t pos = 0;
2736
0
  FreeRDP_PenDevice* pen = freerdp_client_get_pen(cctx, deviceid, &pos);
2737
0
  if (!pen)
2738
0
  {
2739
0
    WLog_WARN(TAG, "unregistered pen device %" PRId32 " event 0x%08" PRIx32, deviceid, flags);
2740
0
    return FALSE;
2741
0
  }
2742
2743
0
  UINT32 fieldFlags = RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT;
2744
0
  UINT32 penFlags =
2745
0
      ((pen->flags & FREERDP_PEN_IS_INVERTED) != 0) ? RDPINPUT_PEN_FLAG_INVERTED : 0;
2746
2747
0
  RdpeiClientContext* rdpei = cctx->rdpei;
2748
0
  WINPR_ASSERT(rdpei);
2749
2750
0
  UINT32 normalizedpressure = 1024;
2751
0
  INT32 x = 0;
2752
0
  INT32 y = 0;
2753
0
  UINT16 rotation = 0;
2754
0
  INT16 tiltX = 0;
2755
0
  INT16 tiltY = 0;
2756
0
  va_list args = WINPR_C_ARRAY_INIT;
2757
0
  va_start(args, deviceid);
2758
2759
0
  x = va_arg(args, INT32);
2760
0
  y = va_arg(args, INT32);
2761
0
  if ((flags & FREERDP_PEN_HAS_PRESSURE) != 0)
2762
0
  {
2763
0
    const double pressure = va_arg(args, double);
2764
0
    const double np = (pressure * 1024.0) / pen->max_pressure;
2765
0
    normalizedpressure = (UINT32)lround(np);
2766
0
    WLog_DBG(TAG, "pen pressure %lf -> %" PRIu32, pressure, normalizedpressure);
2767
0
    fieldFlags |= RDPINPUT_PEN_CONTACT_PRESSURE_PRESENT;
2768
0
  }
2769
0
  if ((flags & FREERDP_PEN_HAS_ROTATION) != 0)
2770
0
  {
2771
0
    const unsigned arg = va_arg(args, unsigned);
2772
0
    rotation = WINPR_ASSERTING_INT_CAST(UINT16, arg);
2773
0
    fieldFlags |= RDPINPUT_PEN_CONTACT_ROTATION_PRESENT;
2774
0
  }
2775
0
  if ((flags & FREERDP_PEN_HAS_TILTX) != 0)
2776
0
  {
2777
0
    const int arg = va_arg(args, int);
2778
0
    tiltX = WINPR_ASSERTING_INT_CAST(INT16, arg);
2779
0
    fieldFlags |= RDPINPUT_PEN_CONTACT_TILTX_PRESENT;
2780
0
  }
2781
0
  if ((flags & FREERDP_PEN_HAS_TILTY) != 0)
2782
0
  {
2783
0
    const int arg = va_arg(args, int);
2784
0
    tiltY = WINPR_ASSERTING_INT_CAST(INT16, arg);
2785
0
    fieldFlags |= RDPINPUT_PEN_CONTACT_TILTY_PRESENT;
2786
0
  }
2787
0
  va_end(args);
2788
2789
0
  if ((flags & FREERDP_PEN_PRESS) != 0)
2790
0
  {
2791
    // Ensure that only one button is pressed
2792
0
    if (pen->pressed)
2793
0
      flags = FREERDP_PEN_MOTION |
2794
0
              (flags & (UINT32) ~(FREERDP_PEN_PRESS | FREERDP_PEN_BARREL_PRESSED));
2795
0
    else if ((flags & FREERDP_PEN_BARREL_PRESSED) != 0)
2796
0
      pen->flags |= FREERDP_PEN_BARREL_PRESSED;
2797
0
  }
2798
0
  else if ((flags & FREERDP_PEN_RELEASE) != 0)
2799
0
  {
2800
0
    if (!pen->pressed ||
2801
0
        ((flags & FREERDP_PEN_BARREL_PRESSED) ^ (pen->flags & FREERDP_PEN_BARREL_PRESSED)))
2802
0
      flags = FREERDP_PEN_MOTION |
2803
0
              (flags & (UINT32) ~(FREERDP_PEN_RELEASE | FREERDP_PEN_BARREL_PRESSED));
2804
0
    else
2805
0
      pen->flags &= (UINT32)~FREERDP_PEN_BARREL_PRESSED;
2806
0
  }
2807
2808
0
  flags |= pen->flags;
2809
0
  if ((flags & FREERDP_PEN_ERASER_PRESSED) != 0)
2810
0
    penFlags |= RDPINPUT_PEN_FLAG_ERASER_PRESSED;
2811
0
  if ((flags & FREERDP_PEN_BARREL_PRESSED) != 0)
2812
0
    penFlags |= RDPINPUT_PEN_FLAG_BARREL_PRESSED;
2813
2814
0
  pen->last_x = x;
2815
0
  pen->last_y = y;
2816
0
  if ((flags & FREERDP_PEN_PRESS) != 0)
2817
0
  {
2818
0
    WLog_DBG(TAG, "Pen press %" PRId32, deviceid);
2819
0
    pen->hovering = FALSE;
2820
0
    pen->pressed = TRUE;
2821
2822
0
    WINPR_ASSERT(rdpei->PenBegin);
2823
0
    const UINT rc = rdpei->PenBegin(rdpei, deviceid, fieldFlags, x, y, penFlags,
2824
0
                                    normalizedpressure, rotation, tiltX, tiltY);
2825
0
    return rc == CHANNEL_RC_OK;
2826
0
  }
2827
0
  else if ((flags & FREERDP_PEN_MOTION) != 0)
2828
0
  {
2829
0
    UINT rc = ERROR_INTERNAL_ERROR;
2830
0
    if (pen->pressed)
2831
0
    {
2832
0
      WLog_DBG(TAG, "Pen update %" PRId32, deviceid);
2833
2834
      // TODO: what if no rotation is supported but tilt is?
2835
0
      WINPR_ASSERT(rdpei->PenUpdate);
2836
0
      rc = rdpei->PenUpdate(rdpei, deviceid, fieldFlags, x, y, penFlags, normalizedpressure,
2837
0
                            rotation, tiltX, tiltY);
2838
0
    }
2839
0
    else if (pen->hovering)
2840
0
    {
2841
0
      WLog_DBG(TAG, "Pen hover update %" PRId32, deviceid);
2842
2843
0
      WINPR_ASSERT(rdpei->PenHoverUpdate);
2844
0
      rc = rdpei->PenHoverUpdate(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
2845
0
                                 penFlags, normalizedpressure, rotation, tiltX, tiltY);
2846
0
    }
2847
0
    else
2848
0
    {
2849
0
      WLog_DBG(TAG, "Pen hover begin %" PRId32, deviceid);
2850
0
      pen->hovering = TRUE;
2851
2852
0
      WINPR_ASSERT(rdpei->PenHoverBegin);
2853
0
      rc = rdpei->PenHoverBegin(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
2854
0
                                penFlags, normalizedpressure, rotation, tiltX, tiltY);
2855
0
    }
2856
0
    return rc == CHANNEL_RC_OK;
2857
0
  }
2858
0
  else if ((flags & FREERDP_PEN_RELEASE) != 0)
2859
0
  {
2860
0
    WLog_DBG(TAG, "Pen release %" PRId32, deviceid);
2861
0
    pen->pressed = FALSE;
2862
0
    pen->hovering = TRUE;
2863
2864
0
    WINPR_ASSERT(rdpei->PenUpdate);
2865
0
    const UINT rc = rdpei->PenUpdate(rdpei, deviceid, fieldFlags, x, y, penFlags,
2866
0
                                     normalizedpressure, rotation, tiltX, tiltY);
2867
0
    if (rc != CHANNEL_RC_OK)
2868
0
      return FALSE;
2869
0
    WINPR_ASSERT(rdpei->PenEnd);
2870
0
    const UINT re = rdpei->PenEnd(rdpei, deviceid, RDPINPUT_PEN_CONTACT_PENFLAGS_PRESENT, x, y,
2871
0
                                  penFlags, normalizedpressure, rotation, tiltX, tiltY);
2872
0
    return re == CHANNEL_RC_OK;
2873
0
  }
2874
2875
0
  WLog_WARN(TAG, "Invalid pen %" PRId32 " flags 0x%08" PRIx32, deviceid, flags);
2876
#else
2877
  WLog_WARN(TAG, "Pen event detected but RDPEI support not compiled in. Recompile with "
2878
                 "-DCHANNEL_RDPEI_CLIENT=ON");
2879
#endif
2880
2881
0
  return FALSE;
2882
0
}
2883
2884
BOOL freerdp_client_pen_cancel_all(rdpClientContext* cctx)
2885
0
{
2886
0
  WINPR_ASSERT(cctx);
2887
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2888
2889
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2890
0
    return FALSE;
2891
2892
0
  const CONNECTION_STATE state = freerdp_get_state(&cctx->context);
2893
0
  if (state != CONNECTION_STATE_ACTIVE)
2894
0
    return TRUE;
2895
2896
0
#if defined(CHANNEL_RDPEI_CLIENT)
2897
0
  RdpeiClientContext* rdpei = cctx->rdpei;
2898
2899
0
  if (!rdpei)
2900
0
    return FALSE;
2901
2902
0
  for (size_t i = 0; i < ARRAYSIZE(cctx->pens); i++)
2903
0
  {
2904
0
    FreeRDP_PenDevice* pen = &cctx->pens[i];
2905
0
    if (pen->hovering)
2906
0
    {
2907
0
      WLog_DBG(TAG, "unhover pen %" PRId32, pen->deviceid);
2908
0
      pen->hovering = FALSE;
2909
0
      const UINT rc =
2910
0
          rdpei->PenHoverCancel(rdpei, pen->deviceid, 0, pen->last_x, pen->last_y);
2911
0
      if (rc != CHANNEL_RC_OK)
2912
0
        return FALSE;
2913
0
    }
2914
0
  }
2915
0
  return TRUE;
2916
#else
2917
  WLog_WARN(TAG, "Pen event detected but RDPEI support not compiled in. Recompile with "
2918
                 "-DCHANNEL_RDPEI_CLIENT=ON");
2919
  return FALSE;
2920
#endif
2921
0
}
2922
2923
BOOL freerdp_client_is_pen(rdpClientContext* cctx, INT32 deviceid)
2924
0
{
2925
0
  WINPR_ASSERT(cctx);
2926
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2927
2928
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2929
0
    return FALSE;
2930
2931
0
  if (deviceid == 0)
2932
0
    return FALSE;
2933
2934
0
  for (size_t x = 0; x < ARRAYSIZE(cctx->pens); x++)
2935
0
  {
2936
0
    const FreeRDP_PenDevice* pen = &cctx->pens[x];
2937
0
    if (pen->deviceid == deviceid)
2938
0
      return TRUE;
2939
0
  }
2940
2941
0
  return FALSE;
2942
0
}
2943
2944
BOOL freerdp_client_use_relative_mouse_events(rdpClientContext* cctx)
2945
0
{
2946
0
  WINPR_ASSERT(cctx);
2947
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
2948
2949
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
2950
0
    return FALSE;
2951
2952
0
  const rdpSettings* settings = cctx->context.settings;
2953
0
  const BOOL useRelative = freerdp_settings_get_bool(settings, FreeRDP_MouseUseRelativeMove);
2954
0
  const BOOL haveRelative = freerdp_settings_get_bool(settings, FreeRDP_HasRelativeMouseEvent);
2955
0
  BOOL ainput = FALSE;
2956
0
#if defined(CHANNEL_AINPUT_CLIENT)
2957
0
  ainput = cctx->ainput != nullptr;
2958
0
#endif
2959
2960
0
  return useRelative && (haveRelative || ainput);
2961
0
}
2962
2963
#if defined(WITH_AAD)
2964
WINPR_ATTR_MALLOC(free, 1)
2965
static char* get_redirect_uri(const rdpSettings* settings)
2966
{
2967
  char* redirect_uri = nullptr;
2968
  const bool cli = freerdp_settings_get_bool(settings, FreeRDP_UseCommonStdioCallbacks);
2969
  if (cli)
2970
  {
2971
    const char* redirect_fmt =
2972
        freerdp_settings_get_string(settings, FreeRDP_GatewayAvdAccessAadFormat);
2973
    const BOOL useTenant = freerdp_settings_get_bool(settings, FreeRDP_GatewayAvdUseTenantid);
2974
    const char* tenantid = "common";
2975
    if (useTenant)
2976
      tenantid = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdAadtenantid);
2977
2978
    if (tenantid && redirect_fmt)
2979
    {
2980
      const char* url =
2981
          freerdp_settings_get_string(settings, FreeRDP_GatewayAzureActiveDirectory);
2982
2983
      size_t redirect_len = 0;
2984
      winpr_asprintf(&redirect_uri, &redirect_len, redirect_fmt, url, tenantid);
2985
    }
2986
  }
2987
  else
2988
  {
2989
    const char* client_id = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdClientID);
2990
    const char* redirect_fmt =
2991
        freerdp_settings_get_string(settings, FreeRDP_GatewayAvdAccessTokenFormat);
2992
2993
    size_t redirect_len = 0;
2994
    winpr_asprintf(&redirect_uri, &redirect_len, redirect_fmt, client_id);
2995
  }
2996
  return redirect_uri;
2997
}
2998
2999
/** @return a "&login_hint=<user>" query parameter pre-selecting the account in the login form,
3000
 *  an empty string when no usable user name is configured, nullptr on allocation failure */
3001
WINPR_ATTR_MALLOC(free, 1)
3002
static char* get_login_hint(const rdpSettings* settings, FreeRDP_Settings_Keys_String primary,
3003
                            FreeRDP_Settings_Keys_String secondary)
3004
{
3005
  const FreeRDP_Settings_Keys_String keys[] = { primary, secondary };
3006
  for (size_t x = 0; x < ARRAYSIZE(keys); x++)
3007
  {
3008
    const char* user = freerdp_settings_get_string(settings, keys[x]);
3009
    /* AAD accounts are UPNs: a DOMAIN\user or bare name would only prefill the form with an
3010
     * account that can't sign in */
3011
    if (!user || !strchr(user, '@') || strchr(user, '\\'))
3012
      continue;
3013
3014
    char* encoded = winpr_str_url_encode(user, strlen(user));
3015
    if (!encoded)
3016
      return nullptr;
3017
3018
    char* param = nullptr;
3019
    size_t len = 0;
3020
    winpr_asprintf(&param, &len, "&login_hint=%s", encoded);
3021
    free(encoded);
3022
    return param;
3023
  }
3024
  return _strdup("");
3025
}
3026
3027
WINPR_ATTR_MALLOC(free, 1)
3028
static char* avd_auth_request(rdpClientContext* cctx, WINPR_ATTR_UNUSED va_list ap, size_t* plen)
3029
{
3030
  WINPR_ASSERT(cctx);
3031
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
3032
  WINPR_ASSERT(plen);
3033
  *plen = 0;
3034
3035
  if (!cctx || !requireRdpClientContext(&cctx->context))
3036
    return nullptr;
3037
3038
  const rdpSettings* settings = cctx->context.settings;
3039
  const char* client_id = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdClientID);
3040
  const char* ep = freerdp_utils_aad_get_wellknown_string(&cctx->context,
3041
                                                          AAD_WELLKNOWN_authorization_endpoint);
3042
  const char* scope = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdScope);
3043
3044
  if (!client_id || !ep || !scope)
3045
    return nullptr;
3046
3047
  char* redirect_uri = get_redirect_uri(settings);
3048
  /* the AVD token is for the gateway: prefer the gateway user */
3049
  char* login_hint = get_login_hint(settings, FreeRDP_GatewayUsername, FreeRDP_Username);
3050
  char* url = nullptr;
3051
  if (redirect_uri && login_hint)
3052
    winpr_asprintf(&url, plen, "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s%s",
3053
                   ep, client_id, scope, redirect_uri, login_hint);
3054
  free(login_hint);
3055
  free(redirect_uri);
3056
  return url;
3057
}
3058
3059
WINPR_ATTR_MALLOC(free, 1)
3060
static char* avd_token_request(rdpClientContext* cctx, WINPR_ATTR_UNUSED va_list ap, size_t* plen)
3061
{
3062
  WINPR_ASSERT(cctx);
3063
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
3064
  WINPR_ASSERT(plen);
3065
  *plen = 0;
3066
3067
  if (!cctx || !requireRdpClientContext(&cctx->context))
3068
    return nullptr;
3069
3070
  const rdpSettings* settings = cctx->context.settings;
3071
  const char* client_id = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdClientID);
3072
  const char* ep = freerdp_utils_aad_get_wellknown_string(&cctx->context,
3073
                                                          AAD_WELLKNOWN_authorization_endpoint);
3074
  const char* scope = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdScope);
3075
3076
  if (!client_id || !ep || !scope)
3077
    return nullptr;
3078
3079
  char* redirect_uri = get_redirect_uri(settings);
3080
  if (!redirect_uri)
3081
    return nullptr;
3082
3083
  char* url = nullptr;
3084
3085
  const char* code = va_arg(ap, const char*);
3086
  winpr_asprintf(&url, plen,
3087
                 "grant_type=authorization_code&code=%s&client_id=%s&scope=%s&redirect_uri=%s",
3088
                 code, client_id, scope, redirect_uri);
3089
  free(redirect_uri);
3090
  return url;
3091
}
3092
3093
WINPR_ATTR_MALLOC(free, 1)
3094
static char* aad_auth_request(rdpClientContext* cctx, WINPR_ATTR_UNUSED va_list ap, size_t* plen)
3095
{
3096
  WINPR_ASSERT(cctx);
3097
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
3098
  WINPR_ASSERT(plen);
3099
3100
  *plen = 0;
3101
  if (!cctx || !requireRdpClientContext(&cctx->context))
3102
    return nullptr;
3103
3104
  const rdpSettings* settings = cctx->context.settings;
3105
  char* url = nullptr;
3106
  char* redirect_uri = get_redirect_uri(settings);
3107
  /* the RDSTLS token is for the session host: prefer the host user */
3108
  char* login_hint = get_login_hint(settings, FreeRDP_Username, FreeRDP_GatewayUsername);
3109
3110
  const char* client_id = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdClientID);
3111
  if (!client_id || !redirect_uri || !login_hint)
3112
    goto cleanup;
3113
3114
  {
3115
    const char* scope = va_arg(ap, const char*);
3116
    if (!scope)
3117
      goto cleanup;
3118
3119
    {
3120
      const char* ep = freerdp_utils_aad_get_wellknown_string(
3121
          &cctx->context, AAD_WELLKNOWN_authorization_endpoint);
3122
      winpr_asprintf(&url, plen,
3123
                     "%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s%s", ep,
3124
                     client_id, scope, redirect_uri, login_hint);
3125
    }
3126
  }
3127
3128
cleanup:
3129
  free(login_hint);
3130
  free(redirect_uri);
3131
  return url;
3132
}
3133
3134
WINPR_ATTR_MALLOC(free, 1)
3135
static char* aad_token_request(rdpClientContext* cctx, WINPR_ATTR_UNUSED va_list ap, size_t* plen)
3136
{
3137
  WINPR_ASSERT(cctx);
3138
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
3139
  WINPR_ASSERT(plen);
3140
  *plen = 0;
3141
3142
  if (!cctx || !requireRdpClientContext(&cctx->context))
3143
    return nullptr;
3144
3145
  const rdpSettings* settings = cctx->context.settings;
3146
  const char* client_id = freerdp_settings_get_string(settings, FreeRDP_GatewayAvdClientID);
3147
  const char* ep = freerdp_utils_aad_get_wellknown_string(&cctx->context,
3148
                                                          AAD_WELLKNOWN_authorization_endpoint);
3149
  const char* scope = va_arg(ap, const char*);
3150
  const char* code = va_arg(ap, const char*);
3151
  const char* req_cnf = va_arg(ap, const char*);
3152
3153
  if (!client_id || !ep || !scope || !code || !req_cnf)
3154
    return nullptr;
3155
3156
  char* redirect_uri = get_redirect_uri(settings);
3157
  if (!redirect_uri)
3158
    return nullptr;
3159
3160
  char* url = nullptr;
3161
3162
  winpr_asprintf(
3163
      &url, plen,
3164
      "grant_type=authorization_code&code=%s&client_id=%s&scope=%s&redirect_uri=%s&req_cnf=%s",
3165
      code, client_id, scope, redirect_uri, req_cnf);
3166
  free(redirect_uri);
3167
  return url;
3168
}
3169
#endif
3170
3171
char* freerdp_client_get_aad_url(rdpClientContext* cctx, freerdp_client_aad_type type, ...)
3172
0
{
3173
0
  WINPR_ASSERT(cctx);
3174
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
3175
3176
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
3177
0
    return nullptr;
3178
3179
0
  char* str = nullptr;
3180
0
  size_t len = 0;
3181
3182
0
  va_list ap = WINPR_C_ARRAY_INIT;
3183
0
  va_start(ap, type);
3184
0
  switch (type)
3185
0
  {
3186
#if defined(WITH_AAD)
3187
    case FREERDP_CLIENT_AAD_AUTH_REQUEST:
3188
      if (freerdp_oauth2_reset(cctx->oauth2))
3189
        str = aad_auth_request(cctx, ap, &len);
3190
      break;
3191
    case FREERDP_CLIENT_AAD_TOKEN_REQUEST:
3192
      str = aad_token_request(cctx, ap, &len);
3193
      break;
3194
    case FREERDP_CLIENT_AAD_AVD_AUTH_REQUEST:
3195
      if (freerdp_oauth2_reset(cctx->oauth2))
3196
        str = avd_auth_request(cctx, ap, &len);
3197
      break;
3198
    case FREERDP_CLIENT_AAD_AVD_TOKEN_REQUEST:
3199
      str = avd_token_request(cctx, ap, &len);
3200
      break;
3201
#endif
3202
0
    default:
3203
0
      break;
3204
0
  }
3205
0
  va_end(ap);
3206
3207
0
  char* safestr = freerdp_oauth2_append_state(cctx->oauth2, str, len, nullptr);
3208
0
  winpr_zfree(str);
3209
0
  return safestr;
3210
0
}
3211
3212
BOOL client_common_save_session_info(WINPR_ATTR_UNUSED rdpContext* context, UINT32 type,
3213
                                     const void* data)
3214
0
{
3215
0
  char buffer[128] = WINPR_C_ARRAY_INIT;
3216
0
  WLog_INFO(TAG, "%s [%s]", freerdp_session_logon_type_str(type),
3217
0
            freerdp_session_logon_type_data_str(type, data, buffer, sizeof(buffer)));
3218
0
  return TRUE;
3219
0
}
3220
3221
char* freerdp_client_extract_aad_code(rdpClientContext* cctx, const char* data, size_t length)
3222
0
{
3223
0
  WINPR_ASSERT(cctx);
3224
0
  WINPR_ASSERT(requireRdpClientContext(&cctx->context));
3225
3226
0
  if (!cctx || !requireRdpClientContext(&cctx->context))
3227
0
    return nullptr;
3228
0
  return freerdp_oauth2_extract_code(cctx->oauth2, data, length);
3229
0
}
3230
3231
WINPR_ATTR_NODISCARD
3232
static BOOL client_helper_get_access_token_va(freerdp* instance,
3233
                                              WINPR_ATTR_UNUSED AccessTokenType tokenType,
3234
                                              WINPR_ATTR_UNUSED char** token,
3235
                                              WINPR_ATTR_UNUSED size_t count, va_list ap)
3236
0
{
3237
0
  WINPR_ASSERT(instance);
3238
0
  WINPR_ASSERT(token);
3239
3240
0
  *token = nullptr;
3241
3242
0
  WINPR_ASSERT(requireRdpClientContext(instance->context));
3243
3244
#if defined(WITH_AAD)
3245
  rdpClientContext* cctx = (rdpClientContext*)instance->context;
3246
  WINPR_ASSERT(cctx);
3247
3248
  /* Keep one helper for the whole client lifetime (it is stopped when the context is freed):
3249
   * its browser session then carries over from one token request to the next, e.g. AVD
3250
   * gateway then session host, instead of prompting for a login each time. Only start a new
3251
   * one if there's none yet or the previous one exited. */
3252
  if (!aad_auth_helper_is_running(cctx->aadHelper))
3253
  {
3254
    aad_auth_helper_stop(cctx->aadHelper);
3255
    cctx->aadHelper = aad_auth_helper_start(cctx);
3256
    if (!cctx->aadHelper)
3257
      return FALSE;
3258
  }
3259
3260
  const BOOL rc =
3261
      aad_auth_helper_get_access_token_v(cctx->aadHelper, tokenType, token, count, ap);
3262
3263
  /* a helper that died during the request (crash, user killed it) is restarted next time */
3264
  if (!rc && !aad_auth_helper_is_running(cctx->aadHelper))
3265
  {
3266
    aad_auth_helper_stop(cctx->aadHelper);
3267
    cctx->aadHelper = nullptr;
3268
  }
3269
  return rc;
3270
#else
3271
0
  WLog_ERR(TAG, "Build does not support AAD authentication");
3272
0
  return FALSE;
3273
0
#endif
3274
0
}
3275
3276
BOOL client_failsafe_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
3277
                                      size_t count, ...)
3278
0
{
3279
0
  {
3280
0
    va_list ap = WINPR_C_ARRAY_INIT;
3281
0
    va_start(ap, count);
3282
0
    const BOOL rc = client_helper_get_access_token_va(instance, tokenType, token, count, ap);
3283
0
    va_end(ap);
3284
0
    if (rc)
3285
0
      return rc;
3286
0
  }
3287
0
  {
3288
0
    va_list ap = WINPR_C_ARRAY_INIT;
3289
0
    va_start(ap, count);
3290
0
    const BOOL rc = client_cli_get_access_token_va(instance, tokenType, token, count, ap);
3291
0
    va_end(ap);
3292
0
    return rc;
3293
0
  }
3294
0
}
3295
3296
BOOL client_helper_get_access_token(freerdp* instance, AccessTokenType tokenType, char** token,
3297
                                    size_t count, ...)
3298
0
{
3299
0
  va_list ap = WINPR_C_ARRAY_INIT;
3300
0
  va_start(ap, count);
3301
0
  const BOOL rc = client_helper_get_access_token_va(instance, tokenType, token, count, ap);
3302
  va_end(ap);
3303
0
  return rc;
3304
0
}