Coverage Report

Created: 2026-10-15 06:31

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