Coverage Report

Created: 2026-07-16 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/libfreerdp/utils/passphrase.c
Line
Count
Source
1
/**
2
 * FreeRDP: A Remote Desktop Protocol Implementation
3
 * Passphrase Handling Utils
4
 *
5
 * Copyright 2011 Shea Levy <shea@shealevy.com>
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
20
#include <winpr/atexit.h>
21
#include <winpr/environment.h>
22
23
#include <freerdp/config.h>
24
#include <freerdp/freerdp.h>
25
26
#include <errno.h>
27
#include <freerdp/utils/passphrase.h>
28
29
#ifdef _WIN32
30
31
#include <stdio.h>
32
#include <string.h>
33
#include <io.h>
34
#include <conio.h>
35
#include <wincred.h>
36
37
static char read_chr(FILE* f)
38
{
39
  char chr;
40
  const BOOL isTty = _isatty(_fileno(f));
41
  if (isTty)
42
    return fgetc(f);
43
  if (fscanf_s(f, "%c", &chr, (UINT32)sizeof(char)) && !feof(f))
44
    return chr;
45
  return 0;
46
}
47
48
int freerdp_interruptible_getc(rdpContext* context, FILE* f)
49
{
50
  return read_chr(f);
51
}
52
53
const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf,
54
                                    size_t bufsiz, int from_stdin)
55
{
56
  if (bufsiz == 0)
57
  {
58
    errno = EINVAL;
59
    return nullptr;
60
  }
61
62
  /* When /from-stdin is requested, read the password from stdin. The Unix
63
   * counterpart (freerdp_passphrase_read_tty) does the same, suppressing
64
   * terminal echo via tcsetattr; suppress console echo here via SetConsoleMode
65
   * when stdin is an interactive console. On a pipe the echo bit is moot. */
66
  if (from_stdin)
67
  {
68
    HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
69
    const BOOL isTty = _isatty(_fileno(stdin)) != 0;
70
    DWORD origMode = 0;
71
    BOOL echoSuppressed = FALSE;
72
73
    if (isTty && hStdin && hStdin != INVALID_HANDLE_VALUE && GetConsoleMode(hStdin, &origMode))
74
    {
75
      if (SetConsoleMode(hStdin, origMode & ~(DWORD)ENABLE_ECHO_INPUT))
76
        echoSuppressed = TRUE;
77
    }
78
79
    if (prompt)
80
    {
81
      (void)fputs(prompt, stdout);
82
      (void)fflush(stdout);
83
    }
84
85
    WINPR_ASSERT(bufsiz <= INT32_MAX);
86
    const char* rc = fgets(buf, (int)bufsiz, stdin);
87
88
    if (echoSuppressed)
89
    {
90
      (void)SetConsoleMode(hStdin, origMode);
91
      (void)fputc('\n', stdout);
92
      (void)fflush(stdout);
93
    }
94
95
    if (!rc)
96
      return nullptr;
97
98
    buf[strcspn(buf, "\r\n")] = '\0';
99
    return buf;
100
  }
101
102
  WCHAR UserNameW[CREDUI_MAX_USERNAME_LENGTH + 1] = { 'p', 'r', 'e', 'f', 'i',
103
                                                    'l', 'l', 'e', 'd', '\0' };
104
  WCHAR PasswordW[CREDUI_MAX_PASSWORD_LENGTH + 1] = WINPR_C_ARRAY_INIT;
105
  BOOL fSave = FALSE;
106
  DWORD dwFlags = 0;
107
  WCHAR* promptW = ConvertUtf8ToWCharAlloc(prompt, nullptr);
108
  const DWORD status =
109
      CredUICmdLinePromptForCredentialsW(promptW, nullptr, 0, UserNameW, ARRAYSIZE(UserNameW),
110
                                         PasswordW, ARRAYSIZE(PasswordW), &fSave, dwFlags);
111
  free(promptW);
112
  if (ConvertWCharNToUtf8(PasswordW, ARRAYSIZE(PasswordW), buf, bufsiz) < 0)
113
    return nullptr;
114
  return buf;
115
}
116
117
const char* freerdp_passphrase_from_env(WINPR_ATTR_UNUSED rdpContext* context,
118
                                        WINPR_ATTR_UNUSED const char* prompt,
119
                                        WINPR_ATTR_UNUSED char* buf,
120
                                        WINPR_ATTR_UNUSED size_t bufsiz)
121
{
122
  return nullptr;
123
}
124
125
const char* freerdp_passphrase_read_tty(WINPR_ATTR_UNUSED rdpContext* context,
126
                                        WINPR_ATTR_UNUSED const char* prompt,
127
                                        WINPR_ATTR_UNUSED char* buf,
128
                                        WINPR_ATTR_UNUSED size_t bufsiz,
129
                                        WINPR_ATTR_UNUSED int from_stdin)
130
{
131
  return nullptr;
132
}
133
134
#elif !defined(ANDROID)
135
136
#include <fcntl.h>
137
#include <stdio.h>
138
#include <string.h>
139
#include <sys/stat.h>
140
#include <sys/wait.h>
141
#include <termios.h>
142
#include <unistd.h>
143
#include <freerdp/utils/signal.h>
144
#include <freerdp/log.h>
145
#if defined(WINPR_HAVE_POLL_H) && !defined(__APPLE__)
146
#include <poll.h>
147
#else
148
#include <time.h>
149
#include <sys/select.h>
150
#endif
151
152
#define TAG FREERDP_TAG("utils.passphrase")
153
154
static int wait_for_fd(int fd, int timeout)
155
0
{
156
0
  int status = 0;
157
0
#if defined(WINPR_HAVE_POLL_H) && !defined(__APPLE__)
158
0
  struct pollfd pollset = WINPR_C_ARRAY_INIT;
159
0
  pollset.fd = fd;
160
0
  pollset.events = POLLIN;
161
0
  pollset.revents = 0;
162
163
0
  do
164
0
  {
165
0
    status = poll(&pollset, 1, timeout);
166
0
  } while ((status < 0) && (errno == EINTR));
167
168
#else
169
  fd_set rset = WINPR_C_ARRAY_INIT;
170
  struct timeval tv = WINPR_C_ARRAY_INIT;
171
  FD_ZERO(&rset);
172
  FD_SET(fd, &rset);
173
174
  if (timeout)
175
  {
176
    tv.tv_sec = timeout / 1000;
177
    tv.tv_usec = (timeout % 1000) * 1000;
178
  }
179
180
  do
181
  {
182
    status = select(fd + 1, &rset, nullptr, nullptr, timeout ? &tv : nullptr);
183
  } while ((status < 0) && (errno == EINTR));
184
185
#endif
186
0
  return status;
187
0
}
188
189
static void replace_char(char* buffer, WINPR_ATTR_UNUSED size_t buffer_len, const char* toreplace)
190
0
{
191
0
  while (*toreplace != '\0')
192
0
  {
193
0
    char* ptr = nullptr;
194
0
    while ((ptr = strrchr(buffer, *toreplace)) != nullptr)
195
0
      *ptr = '\0';
196
0
    toreplace++;
197
0
  }
198
0
}
199
200
const char* freerdp_passphrase_read_tty(rdpContext* context, const char* prompt, char* buf,
201
                                        size_t bufsiz, int from_stdin)
202
0
{
203
0
  BOOL terminal_needs_reset = FALSE;
204
0
  char term_name[L_ctermid] = WINPR_C_ARRAY_INIT;
205
206
0
  FILE* fout = nullptr;
207
208
0
  if (bufsiz == 0)
209
0
  {
210
0
    errno = EINVAL;
211
0
    return nullptr;
212
0
  }
213
214
0
  ctermid(term_name);
215
0
  int terminal_fildes = 0;
216
0
  if (from_stdin || (strcmp(term_name, "") == 0))
217
0
  {
218
0
    fout = stdout;
219
0
    terminal_fildes = STDIN_FILENO;
220
0
  }
221
0
  else
222
0
  {
223
0
    const int term_file = open(term_name, O_RDWR);
224
0
    if (term_file < 0)
225
0
    {
226
0
      fout = stdout;
227
0
      terminal_fildes = STDIN_FILENO;
228
0
    }
229
0
    else
230
0
    {
231
0
      fout = fdopen(term_file, "w");
232
0
      if (!fout)
233
0
      {
234
0
        close(term_file);
235
0
        return nullptr;
236
0
      }
237
0
      terminal_fildes = term_file;
238
0
    }
239
0
  }
240
241
0
  struct termios orig_flags = WINPR_C_ARRAY_INIT;
242
0
  if (tcgetattr(terminal_fildes, &orig_flags) != -1)
243
0
  {
244
0
    struct termios new_flags = WINPR_C_ARRAY_INIT;
245
0
    new_flags = orig_flags;
246
0
    new_flags.c_lflag &= (uint32_t)~ECHO;
247
0
    new_flags.c_lflag |= ECHONL;
248
0
    terminal_needs_reset = TRUE;
249
0
    if (tcsetattr(terminal_fildes, TCSAFLUSH, &new_flags) == -1)
250
0
      terminal_needs_reset = FALSE;
251
0
  }
252
253
0
  FILE* fp = fdopen(terminal_fildes, "r");
254
0
  if (!fp)
255
0
    goto error;
256
257
0
  (void)fprintf(fout, "%s", prompt);
258
0
  (void)fflush(fout);
259
260
0
  {
261
0
    char* ptr = nullptr;
262
0
    size_t ptr_len = 0;
263
0
    const SSIZE_T res = freerdp_interruptible_get_line(context, &ptr, &ptr_len, fp);
264
0
    if (res < 0)
265
0
      goto error;
266
267
0
    replace_char(ptr, ptr_len, "\r\n");
268
269
0
    strncpy(buf, ptr, MIN(bufsiz, ptr_len));
270
0
    free(ptr);
271
0
  }
272
273
0
  if (terminal_needs_reset)
274
0
  {
275
0
    if (tcsetattr(terminal_fildes, TCSAFLUSH, &orig_flags) == -1)
276
0
      goto error;
277
0
  }
278
279
0
  if (terminal_fildes != STDIN_FILENO)
280
0
    (void)fclose(fp);
281
282
0
  return buf;
283
284
0
error:
285
0
{
286
  // NOLINTNEXTLINE(clang-analyzer-unix.Stream)
287
0
  int saved_errno = errno;
288
0
  if (terminal_needs_reset)
289
0
    (void)tcsetattr(terminal_fildes, TCSAFLUSH, &orig_flags);
290
291
0
  if (terminal_fildes != STDIN_FILENO)
292
0
  {
293
0
    if (fp)
294
0
      (void)fclose(fp);
295
0
  }
296
  // NOLINTNEXTLINE(clang-analyzer-unix.Stream)
297
0
  errno = saved_errno;
298
0
}
299
300
0
  return nullptr;
301
0
}
302
303
static const char* freerdp_passphrase_read_askpass(const char* prompt, char* buf, size_t bufsiz,
304
                                                   char const* askpass_env)
305
0
{
306
0
  char command[4096] = WINPR_C_ARRAY_INIT;
307
308
0
  (void)sprintf_s(command, sizeof(command), "%s 'FreeRDP authentication\n%s'", askpass_env,
309
0
                  prompt);
310
  // NOLINTNEXTLINE(clang-analyzer-optin.taint.GenericTaint,bugprone-command-processor)
311
0
  FILE* askproc = popen(command, "r");
312
0
  if (!askproc)
313
0
    return nullptr;
314
0
  WINPR_ASSERT(bufsiz <= INT32_MAX);
315
0
  if (fgets(buf, (int)bufsiz, askproc) != nullptr)
316
0
    buf[strcspn(buf, "\r\n")] = '\0';
317
0
  else
318
0
    buf = nullptr;
319
0
  const int status = pclose(askproc);
320
0
  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
321
0
    buf = nullptr;
322
323
0
  return buf;
324
0
}
325
326
const char* freerdp_passphrase_from_env(WINPR_ATTR_UNUSED rdpContext* context, const char* prompt,
327
                                        char* buf, size_t bufsiz)
328
0
{
329
  // NOLINTNEXTLINE(concurrency-mt-unsafe)
330
0
  const char* askpass_env = getenv("FREERDP_ASKPASS");
331
0
  if (!askpass_env)
332
0
    return nullptr;
333
0
  return freerdp_passphrase_read_askpass(prompt, buf, bufsiz, askpass_env);
334
0
}
335
336
const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf,
337
                                    size_t bufsiz, int from_stdin)
338
0
{
339
0
  const char* askpass_env = freerdp_passphrase_from_env(context, prompt, buf, bufsiz);
340
0
  if (askpass_env)
341
0
    return askpass_env;
342
343
0
  return freerdp_passphrase_read_tty(context, prompt, buf, bufsiz, from_stdin);
344
0
}
345
346
static BOOL set_termianl_nonblock(int ifd, BOOL nonblock);
347
348
static void restore_terminal(void)
349
0
{
350
0
  (void)set_termianl_nonblock(-1, FALSE);
351
0
}
352
353
BOOL set_termianl_nonblock(int ifd, BOOL nonblock)
354
0
{
355
0
  static int fd = -1;
356
0
  static bool registered = false;
357
0
  static int orig = 0;
358
0
  static struct termios termios = WINPR_C_ARRAY_INIT;
359
360
0
  if (ifd >= 0)
361
0
    fd = ifd;
362
363
0
  if (fd < 0)
364
0
    return FALSE;
365
366
0
  if (nonblock)
367
0
  {
368
0
    if (!registered)
369
0
    {
370
0
      (void)winpr_atexit(restore_terminal);
371
0
      registered = true;
372
0
    }
373
374
0
    const int rc1 = fcntl(fd, F_SETFL, orig | O_NONBLOCK);
375
0
    if (rc1 != 0)
376
0
    {
377
0
      char buffer[128] = WINPR_C_ARRAY_INIT;
378
0
      WLog_ERR(TAG, "fcntl(F_SETFL) failed with %s",
379
0
               winpr_strerror(errno, buffer, sizeof(buffer)));
380
0
      return FALSE;
381
0
    }
382
0
    const int rc2 = tcgetattr(fd, &termios);
383
0
    if (rc2 != 0)
384
0
    {
385
0
      char buffer[128] = WINPR_C_ARRAY_INIT;
386
0
      WLog_ERR(TAG, "tcgetattr() failed with %s",
387
0
               winpr_strerror(errno, buffer, sizeof(buffer)));
388
0
      return FALSE;
389
0
    }
390
391
0
    struct termios now = termios;
392
0
    cfmakeraw(&now);
393
0
    const int rc3 = tcsetattr(fd, TCSANOW, &now);
394
0
    if (rc3 != 0)
395
0
    {
396
0
      char buffer[128] = WINPR_C_ARRAY_INIT;
397
0
      WLog_ERR(TAG, "tcsetattr(TCSANOW) failed with %s",
398
0
               winpr_strerror(errno, buffer, sizeof(buffer)));
399
0
      return FALSE;
400
0
    }
401
0
  }
402
0
  else
403
0
  {
404
0
    const int rc1 = tcsetattr(fd, TCSANOW, &termios);
405
0
    if (rc1 != 0)
406
0
    {
407
0
      char buffer[128] = WINPR_C_ARRAY_INIT;
408
0
      WLog_ERR(TAG, "tcsetattr(TCSANOW) failed with %s",
409
0
               winpr_strerror(errno, buffer, sizeof(buffer)));
410
0
      return FALSE;
411
0
    }
412
0
    const int rc2 = fcntl(fd, F_SETFL, orig);
413
0
    if (rc2 != 0)
414
0
    {
415
0
      char buffer[128] = WINPR_C_ARRAY_INIT;
416
0
      WLog_ERR(TAG, "fcntl(F_SETFL) failed with %s",
417
0
               winpr_strerror(errno, buffer, sizeof(buffer)));
418
0
      return FALSE;
419
0
    }
420
0
    fd = -1;
421
0
  }
422
0
  return TRUE;
423
0
}
424
425
int freerdp_interruptible_getc(rdpContext* context, FILE* stream)
426
0
{
427
0
  int rc = EOF;
428
0
  const int fd = fileno(stream);
429
430
0
  (void)set_termianl_nonblock(fd, TRUE);
431
432
0
  do
433
0
  {
434
0
    const int res = wait_for_fd(fd, 10);
435
0
    if (res != 0)
436
0
    {
437
0
      char c = 0;
438
0
      const ssize_t rd = read(fd, &c, 1);
439
0
      if (rd == 1)
440
0
      {
441
0
        if (c == 3) /* ctrl + c */
442
0
          return EOF;
443
0
        if (c == 4) /* ctrl + d */
444
0
          return EOF;
445
0
        if (c == 26) /* ctrl + z */
446
0
          return EOF;
447
0
        rc = (int)c;
448
0
      }
449
0
      break;
450
0
    }
451
0
  } while (!freerdp_shall_disconnect_context(context));
452
453
0
  (void)set_termianl_nonblock(fd, FALSE);
454
455
0
  return rc;
456
0
}
457
458
#else
459
460
const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf,
461
                                    size_t bufsiz, int from_stdin)
462
{
463
  return nullptr;
464
}
465
466
int freerdp_interruptible_getc(rdpContext* context, FILE* f)
467
{
468
  return EOF;
469
}
470
471
const char* freerdp_passphrase_from_env(WINPR_ATTR_UNUSED rdpContext* context,
472
                                        WINPR_ATTR_UNUSED const char* prompt,
473
                                        WINPR_ATTR_UNUSED char* buf,
474
                                        WINPR_ATTR_UNUSED size_t bufsiz)
475
{
476
  return nullptr;
477
}
478
479
const char* freerdp_passphrase_read_tty(WINPR_ATTR_UNUSED rdpContext* context,
480
                                        WINPR_ATTR_UNUSED const char* prompt,
481
                                        WINPR_ATTR_UNUSED char* buf,
482
                                        WINPR_ATTR_UNUSED size_t bufsiz,
483
                                        WINPR_ATTR_UNUSED int from_stdin)
484
{
485
  return nullptr;
486
}
487
#endif
488
489
SSIZE_T freerdp_interruptible_get_line(rdpContext* context, char** plineptr, size_t* psize,
490
                                       FILE* stream)
491
0
{
492
0
  int c = 0;
493
0
  char* n = nullptr;
494
0
  size_t step = 32;
495
0
  size_t used = 0;
496
0
  char* ptr = nullptr;
497
0
  size_t len = 0;
498
499
0
  if (!plineptr || !psize)
500
0
  {
501
0
    errno = EINVAL;
502
0
    return -1;
503
0
  }
504
505
0
  bool echo = true;
506
0
#if !defined(_WIN32) && !defined(ANDROID)
507
0
  {
508
0
    const int fd = fileno(stream);
509
510
0
    struct termios termios = WINPR_C_ARRAY_INIT;
511
    /* This might fail if /from-stdin is used. */
512
0
    if (tcgetattr(fd, &termios) == 0)
513
0
      echo = (termios.c_lflag & ECHO) != 0;
514
0
    else
515
0
      echo = false;
516
0
  }
517
0
#endif
518
519
0
  if (*plineptr && (*psize > 0))
520
0
  {
521
0
    ptr = *plineptr;
522
0
    used = *psize;
523
0
    if (echo)
524
0
    {
525
0
      printf("%s", ptr);
526
0
      (void)fflush(stdout);
527
0
    }
528
0
  }
529
530
0
  do
531
0
  {
532
0
    if (used + 2 >= len)
533
0
    {
534
0
      len += step;
535
0
      n = realloc(ptr, len);
536
537
0
      if (!n)
538
0
      {
539
0
        free(ptr);
540
0
        *plineptr = nullptr;
541
0
        return -1;
542
0
      }
543
544
0
      ptr = n;
545
0
    }
546
547
0
    c = freerdp_interruptible_getc(context, stream);
548
0
    if (c == 127)
549
0
    {
550
0
      if (used > 0)
551
0
      {
552
0
        ptr[used--] = '\0';
553
0
        if (echo)
554
0
        {
555
0
          printf("\b");
556
0
          printf(" ");
557
0
          printf("\b");
558
0
          (void)fflush(stdout);
559
0
        }
560
0
      }
561
0
      continue;
562
0
    }
563
0
    if (echo)
564
0
    {
565
0
      printf("%c", c);
566
0
      (void)fflush(stdout);
567
0
    }
568
0
    if (c != EOF)
569
0
      ptr[used++] = (char)c;
570
0
  } while ((c != '\n') && (c != '\r') && (c != EOF));
571
572
0
  printf("\n");
573
0
  ptr[used] = '\0';
574
0
  if (c == EOF)
575
0
  {
576
0
    free(ptr);
577
0
    *plineptr = nullptr;
578
0
    return EOF;
579
0
  }
580
0
  *plineptr = ptr;
581
0
  *psize = used;
582
0
  return WINPR_ASSERTING_INT_CAST(SSIZE_T, used);
583
0
}