Coverage Report

Created: 2026-06-15 06:57

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
#elif !defined(ANDROID)
118
119
#include <fcntl.h>
120
#include <stdio.h>
121
#include <string.h>
122
#include <sys/stat.h>
123
#include <sys/wait.h>
124
#include <termios.h>
125
#include <unistd.h>
126
#include <freerdp/utils/signal.h>
127
#include <freerdp/log.h>
128
#if defined(WINPR_HAVE_POLL_H) && !defined(__APPLE__)
129
#include <poll.h>
130
#else
131
#include <time.h>
132
#include <sys/select.h>
133
#endif
134
135
#define TAG FREERDP_TAG("utils.passphrase")
136
137
static int wait_for_fd(int fd, int timeout)
138
0
{
139
0
  int status = 0;
140
0
#if defined(WINPR_HAVE_POLL_H) && !defined(__APPLE__)
141
0
  struct pollfd pollset = WINPR_C_ARRAY_INIT;
142
0
  pollset.fd = fd;
143
0
  pollset.events = POLLIN;
144
0
  pollset.revents = 0;
145
146
0
  do
147
0
  {
148
0
    status = poll(&pollset, 1, timeout);
149
0
  } while ((status < 0) && (errno == EINTR));
150
151
#else
152
  fd_set rset = WINPR_C_ARRAY_INIT;
153
  struct timeval tv = WINPR_C_ARRAY_INIT;
154
  FD_ZERO(&rset);
155
  FD_SET(fd, &rset);
156
157
  if (timeout)
158
  {
159
    tv.tv_sec = timeout / 1000;
160
    tv.tv_usec = (timeout % 1000) * 1000;
161
  }
162
163
  do
164
  {
165
    status = select(fd + 1, &rset, nullptr, nullptr, timeout ? &tv : nullptr);
166
  } while ((status < 0) && (errno == EINTR));
167
168
#endif
169
0
  return status;
170
0
}
171
172
static void replace_char(char* buffer, WINPR_ATTR_UNUSED size_t buffer_len, const char* toreplace)
173
0
{
174
0
  while (*toreplace != '\0')
175
0
  {
176
0
    char* ptr = nullptr;
177
0
    while ((ptr = strrchr(buffer, *toreplace)) != nullptr)
178
0
      *ptr = '\0';
179
0
    toreplace++;
180
0
  }
181
0
}
182
183
static const char* freerdp_passphrase_read_tty(rdpContext* context, const char* prompt, char* buf,
184
                                               size_t bufsiz, int from_stdin)
185
0
{
186
0
  BOOL terminal_needs_reset = FALSE;
187
0
  char term_name[L_ctermid] = WINPR_C_ARRAY_INIT;
188
189
0
  FILE* fout = nullptr;
190
191
0
  if (bufsiz == 0)
192
0
  {
193
0
    errno = EINVAL;
194
0
    return nullptr;
195
0
  }
196
197
0
  ctermid(term_name);
198
0
  int terminal_fildes = 0;
199
0
  if (from_stdin || (strcmp(term_name, "") == 0))
200
0
  {
201
0
    fout = stdout;
202
0
    terminal_fildes = STDIN_FILENO;
203
0
  }
204
0
  else
205
0
  {
206
0
    const int term_file = open(term_name, O_RDWR);
207
0
    if (term_file < 0)
208
0
    {
209
0
      fout = stdout;
210
0
      terminal_fildes = STDIN_FILENO;
211
0
    }
212
0
    else
213
0
    {
214
0
      fout = fdopen(term_file, "w");
215
0
      if (!fout)
216
0
      {
217
0
        close(term_file);
218
0
        return nullptr;
219
0
      }
220
0
      terminal_fildes = term_file;
221
0
    }
222
0
  }
223
224
0
  struct termios orig_flags = WINPR_C_ARRAY_INIT;
225
0
  if (tcgetattr(terminal_fildes, &orig_flags) != -1)
226
0
  {
227
0
    struct termios new_flags = WINPR_C_ARRAY_INIT;
228
0
    new_flags = orig_flags;
229
0
    new_flags.c_lflag &= (uint32_t)~ECHO;
230
0
    new_flags.c_lflag |= ECHONL;
231
0
    terminal_needs_reset = TRUE;
232
0
    if (tcsetattr(terminal_fildes, TCSAFLUSH, &new_flags) == -1)
233
0
      terminal_needs_reset = FALSE;
234
0
  }
235
236
0
  FILE* fp = fdopen(terminal_fildes, "r");
237
0
  if (!fp)
238
0
    goto error;
239
240
0
  (void)fprintf(fout, "%s", prompt);
241
0
  (void)fflush(fout);
242
243
0
  {
244
0
    char* ptr = nullptr;
245
0
    size_t ptr_len = 0;
246
0
    const SSIZE_T res = freerdp_interruptible_get_line(context, &ptr, &ptr_len, fp);
247
0
    if (res < 0)
248
0
      goto error;
249
250
0
    replace_char(ptr, ptr_len, "\r\n");
251
252
0
    strncpy(buf, ptr, MIN(bufsiz, ptr_len));
253
0
    free(ptr);
254
0
  }
255
256
0
  if (terminal_needs_reset)
257
0
  {
258
0
    if (tcsetattr(terminal_fildes, TCSAFLUSH, &orig_flags) == -1)
259
0
      goto error;
260
0
  }
261
262
0
  if (terminal_fildes != STDIN_FILENO)
263
0
    (void)fclose(fp);
264
265
0
  return buf;
266
267
0
error:
268
0
{
269
  // NOLINTNEXTLINE(clang-analyzer-unix.Stream)
270
0
  int saved_errno = errno;
271
0
  if (terminal_needs_reset)
272
0
    (void)tcsetattr(terminal_fildes, TCSAFLUSH, &orig_flags);
273
274
0
  if (terminal_fildes != STDIN_FILENO)
275
0
  {
276
0
    if (fp)
277
0
      (void)fclose(fp);
278
0
  }
279
  // NOLINTNEXTLINE(clang-analyzer-unix.Stream)
280
0
  errno = saved_errno;
281
0
}
282
283
0
  return nullptr;
284
0
}
285
286
static const char* freerdp_passphrase_read_askpass(const char* prompt, char* buf, size_t bufsiz,
287
                                                   char const* askpass_env)
288
0
{
289
0
  char command[4096] = WINPR_C_ARRAY_INIT;
290
291
0
  (void)sprintf_s(command, sizeof(command), "%s 'FreeRDP authentication\n%s'", askpass_env,
292
0
                  prompt);
293
  // NOLINTNEXTLINE(clang-analyzer-optin.taint.GenericTaint,bugprone-command-processor)
294
0
  FILE* askproc = popen(command, "r");
295
0
  if (!askproc)
296
0
    return nullptr;
297
0
  WINPR_ASSERT(bufsiz <= INT32_MAX);
298
0
  if (fgets(buf, (int)bufsiz, askproc) != nullptr)
299
0
    buf[strcspn(buf, "\r\n")] = '\0';
300
0
  else
301
0
    buf = nullptr;
302
0
  const int status = pclose(askproc);
303
0
  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
304
0
    buf = nullptr;
305
306
0
  return buf;
307
0
}
308
309
const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf,
310
                                    size_t bufsiz, int from_stdin)
311
0
{
312
  // NOLINTNEXTLINE(concurrency-mt-unsafe)
313
0
  const char* askpass_env = getenv("FREERDP_ASKPASS");
314
315
0
  if (askpass_env)
316
0
    return freerdp_passphrase_read_askpass(prompt, buf, bufsiz, askpass_env);
317
0
  else
318
0
    return freerdp_passphrase_read_tty(context, prompt, buf, bufsiz, from_stdin);
319
0
}
320
321
static BOOL set_termianl_nonblock(int ifd, BOOL nonblock);
322
323
static void restore_terminal(void)
324
0
{
325
0
  (void)set_termianl_nonblock(-1, FALSE);
326
0
}
327
328
BOOL set_termianl_nonblock(int ifd, BOOL nonblock)
329
0
{
330
0
  static int fd = -1;
331
0
  static bool registered = false;
332
0
  static int orig = 0;
333
0
  static struct termios termios = WINPR_C_ARRAY_INIT;
334
335
0
  if (ifd >= 0)
336
0
    fd = ifd;
337
338
0
  if (fd < 0)
339
0
    return FALSE;
340
341
0
  if (nonblock)
342
0
  {
343
0
    if (!registered)
344
0
    {
345
0
      (void)winpr_atexit(restore_terminal);
346
0
      registered = true;
347
0
    }
348
349
0
    const int rc1 = fcntl(fd, F_SETFL, orig | O_NONBLOCK);
350
0
    if (rc1 != 0)
351
0
    {
352
0
      char buffer[128] = WINPR_C_ARRAY_INIT;
353
0
      WLog_ERR(TAG, "fcntl(F_SETFL) failed with %s",
354
0
               winpr_strerror(errno, buffer, sizeof(buffer)));
355
0
      return FALSE;
356
0
    }
357
0
    const int rc2 = tcgetattr(fd, &termios);
358
0
    if (rc2 != 0)
359
0
    {
360
0
      char buffer[128] = WINPR_C_ARRAY_INIT;
361
0
      WLog_ERR(TAG, "tcgetattr() failed with %s",
362
0
               winpr_strerror(errno, buffer, sizeof(buffer)));
363
0
      return FALSE;
364
0
    }
365
366
0
    struct termios now = termios;
367
0
    cfmakeraw(&now);
368
0
    const int rc3 = tcsetattr(fd, TCSANOW, &now);
369
0
    if (rc3 != 0)
370
0
    {
371
0
      char buffer[128] = WINPR_C_ARRAY_INIT;
372
0
      WLog_ERR(TAG, "tcsetattr(TCSANOW) failed with %s",
373
0
               winpr_strerror(errno, buffer, sizeof(buffer)));
374
0
      return FALSE;
375
0
    }
376
0
  }
377
0
  else
378
0
  {
379
0
    const int rc1 = tcsetattr(fd, TCSANOW, &termios);
380
0
    if (rc1 != 0)
381
0
    {
382
0
      char buffer[128] = WINPR_C_ARRAY_INIT;
383
0
      WLog_ERR(TAG, "tcsetattr(TCSANOW) failed with %s",
384
0
               winpr_strerror(errno, buffer, sizeof(buffer)));
385
0
      return FALSE;
386
0
    }
387
0
    const int rc2 = fcntl(fd, F_SETFL, orig);
388
0
    if (rc2 != 0)
389
0
    {
390
0
      char buffer[128] = WINPR_C_ARRAY_INIT;
391
0
      WLog_ERR(TAG, "fcntl(F_SETFL) failed with %s",
392
0
               winpr_strerror(errno, buffer, sizeof(buffer)));
393
0
      return FALSE;
394
0
    }
395
0
    fd = -1;
396
0
  }
397
0
  return TRUE;
398
0
}
399
400
int freerdp_interruptible_getc(rdpContext* context, FILE* stream)
401
0
{
402
0
  int rc = EOF;
403
0
  const int fd = fileno(stream);
404
405
0
  (void)set_termianl_nonblock(fd, TRUE);
406
407
0
  do
408
0
  {
409
0
    const int res = wait_for_fd(fd, 10);
410
0
    if (res != 0)
411
0
    {
412
0
      char c = 0;
413
0
      const ssize_t rd = read(fd, &c, 1);
414
0
      if (rd == 1)
415
0
      {
416
0
        if (c == 3) /* ctrl + c */
417
0
          return EOF;
418
0
        if (c == 4) /* ctrl + d */
419
0
          return EOF;
420
0
        if (c == 26) /* ctrl + z */
421
0
          return EOF;
422
0
        rc = (int)c;
423
0
      }
424
0
      break;
425
0
    }
426
0
  } while (!freerdp_shall_disconnect_context(context));
427
428
0
  (void)set_termianl_nonblock(fd, FALSE);
429
430
0
  return rc;
431
0
}
432
433
#else
434
435
const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf,
436
                                    size_t bufsiz, int from_stdin)
437
{
438
  return nullptr;
439
}
440
441
int freerdp_interruptible_getc(rdpContext* context, FILE* f)
442
{
443
  return EOF;
444
}
445
#endif
446
447
SSIZE_T freerdp_interruptible_get_line(rdpContext* context, char** plineptr, size_t* psize,
448
                                       FILE* stream)
449
0
{
450
0
  int c = 0;
451
0
  char* n = nullptr;
452
0
  size_t step = 32;
453
0
  size_t used = 0;
454
0
  char* ptr = nullptr;
455
0
  size_t len = 0;
456
457
0
  if (!plineptr || !psize)
458
0
  {
459
0
    errno = EINVAL;
460
0
    return -1;
461
0
  }
462
463
0
  bool echo = true;
464
0
#if !defined(_WIN32) && !defined(ANDROID)
465
0
  {
466
0
    const int fd = fileno(stream);
467
468
0
    struct termios termios = WINPR_C_ARRAY_INIT;
469
    /* This might fail if /from-stdin is used. */
470
0
    if (tcgetattr(fd, &termios) == 0)
471
0
      echo = (termios.c_lflag & ECHO) != 0;
472
0
    else
473
0
      echo = false;
474
0
  }
475
0
#endif
476
477
0
  if (*plineptr && (*psize > 0))
478
0
  {
479
0
    ptr = *plineptr;
480
0
    used = *psize;
481
0
    if (echo)
482
0
    {
483
0
      printf("%s", ptr);
484
0
      (void)fflush(stdout);
485
0
    }
486
0
  }
487
488
0
  do
489
0
  {
490
0
    if (used + 2 >= len)
491
0
    {
492
0
      len += step;
493
0
      n = realloc(ptr, len);
494
495
0
      if (!n)
496
0
      {
497
0
        free(ptr);
498
0
        *plineptr = nullptr;
499
0
        return -1;
500
0
      }
501
502
0
      ptr = n;
503
0
    }
504
505
0
    c = freerdp_interruptible_getc(context, stream);
506
0
    if (c == 127)
507
0
    {
508
0
      if (used > 0)
509
0
      {
510
0
        ptr[used--] = '\0';
511
0
        if (echo)
512
0
        {
513
0
          printf("\b");
514
0
          printf(" ");
515
0
          printf("\b");
516
0
          (void)fflush(stdout);
517
0
        }
518
0
      }
519
0
      continue;
520
0
    }
521
0
    if (echo)
522
0
    {
523
0
      printf("%c", c);
524
0
      (void)fflush(stdout);
525
0
    }
526
0
    if (c != EOF)
527
0
      ptr[used++] = (char)c;
528
0
  } while ((c != '\n') && (c != '\r') && (c != EOF));
529
530
0
  printf("\n");
531
0
  ptr[used] = '\0';
532
0
  if (c == EOF)
533
0
  {
534
0
    free(ptr);
535
0
    *plineptr = nullptr;
536
0
    return EOF;
537
0
  }
538
0
  *plineptr = ptr;
539
0
  *psize = used;
540
0
  return WINPR_ASSERTING_INT_CAST(SSIZE_T, used);
541
0
}