Coverage Report

Created: 2026-04-12 07:03

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 <io.h>
33
#include <conio.h>
34
#include <wincred.h>
35
36
static char read_chr(FILE* f)
37
{
38
  char chr;
39
  const BOOL isTty = _isatty(_fileno(f));
40
  if (isTty)
41
    return fgetc(f);
42
  if (fscanf_s(f, "%c", &chr, (UINT32)sizeof(char)) && !feof(f))
43
    return chr;
44
  return 0;
45
}
46
47
int freerdp_interruptible_getc(rdpContext* context, FILE* f)
48
{
49
  return read_chr(f);
50
}
51
52
const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf,
53
                                    size_t bufsiz, int from_stdin)
54
{
55
  WCHAR UserNameW[CREDUI_MAX_USERNAME_LENGTH + 1] = { 'p', 'r', 'e', 'f', 'i',
56
                                                    'l', 'l', 'e', 'd', '\0' };
57
  WCHAR PasswordW[CREDUI_MAX_PASSWORD_LENGTH + 1] = WINPR_C_ARRAY_INIT;
58
  BOOL fSave = FALSE;
59
  DWORD dwFlags = 0;
60
  WCHAR* promptW = ConvertUtf8ToWCharAlloc(prompt, nullptr);
61
  const DWORD status =
62
      CredUICmdLinePromptForCredentialsW(promptW, nullptr, 0, UserNameW, ARRAYSIZE(UserNameW),
63
                                         PasswordW, ARRAYSIZE(PasswordW), &fSave, dwFlags);
64
  free(promptW);
65
  if (ConvertWCharNToUtf8(PasswordW, ARRAYSIZE(PasswordW), buf, bufsiz) < 0)
66
    return nullptr;
67
  return buf;
68
}
69
70
#elif !defined(ANDROID)
71
72
#include <fcntl.h>
73
#include <stdio.h>
74
#include <string.h>
75
#include <sys/stat.h>
76
#include <sys/wait.h>
77
#include <termios.h>
78
#include <unistd.h>
79
#include <freerdp/utils/signal.h>
80
#include <freerdp/log.h>
81
#if defined(WINPR_HAVE_POLL_H) && !defined(__APPLE__)
82
#include <poll.h>
83
#else
84
#include <time.h>
85
#include <sys/select.h>
86
#endif
87
88
#define TAG FREERDP_TAG("utils.passphrase")
89
90
static int wait_for_fd(int fd, int timeout)
91
0
{
92
0
  int status = 0;
93
0
#if defined(WINPR_HAVE_POLL_H) && !defined(__APPLE__)
94
0
  struct pollfd pollset = WINPR_C_ARRAY_INIT;
95
0
  pollset.fd = fd;
96
0
  pollset.events = POLLIN;
97
0
  pollset.revents = 0;
98
99
0
  do
100
0
  {
101
0
    status = poll(&pollset, 1, timeout);
102
0
  } while ((status < 0) && (errno == EINTR));
103
104
#else
105
  fd_set rset = WINPR_C_ARRAY_INIT;
106
  struct timeval tv = WINPR_C_ARRAY_INIT;
107
  FD_ZERO(&rset);
108
  FD_SET(fd, &rset);
109
110
  if (timeout)
111
  {
112
    tv.tv_sec = timeout / 1000;
113
    tv.tv_usec = (timeout % 1000) * 1000;
114
  }
115
116
  do
117
  {
118
    status = select(fd + 1, &rset, nullptr, nullptr, timeout ? &tv : nullptr);
119
  } while ((status < 0) && (errno == EINTR));
120
121
#endif
122
0
  return status;
123
0
}
124
125
static void replace_char(char* buffer, WINPR_ATTR_UNUSED size_t buffer_len, const char* toreplace)
126
0
{
127
0
  while (*toreplace != '\0')
128
0
  {
129
0
    char* ptr = nullptr;
130
0
    while ((ptr = strrchr(buffer, *toreplace)) != nullptr)
131
0
      *ptr = '\0';
132
0
    toreplace++;
133
0
  }
134
0
}
135
136
static const char* freerdp_passphrase_read_tty(rdpContext* context, const char* prompt, char* buf,
137
                                               size_t bufsiz, int from_stdin)
138
0
{
139
0
  BOOL terminal_needs_reset = FALSE;
140
0
  char term_name[L_ctermid] = WINPR_C_ARRAY_INIT;
141
142
0
  FILE* fout = nullptr;
143
144
0
  if (bufsiz == 0)
145
0
  {
146
0
    errno = EINVAL;
147
0
    return nullptr;
148
0
  }
149
150
0
  ctermid(term_name);
151
0
  int terminal_fildes = 0;
152
0
  if (from_stdin || (strcmp(term_name, "") == 0))
153
0
  {
154
0
    fout = stdout;
155
0
    terminal_fildes = STDIN_FILENO;
156
0
  }
157
0
  else
158
0
  {
159
0
    const int term_file = open(term_name, O_RDWR);
160
0
    if (term_file < 0)
161
0
    {
162
0
      fout = stdout;
163
0
      terminal_fildes = STDIN_FILENO;
164
0
    }
165
0
    else
166
0
    {
167
0
      fout = fdopen(term_file, "w");
168
0
      if (!fout)
169
0
      {
170
0
        close(term_file);
171
0
        return nullptr;
172
0
      }
173
0
      terminal_fildes = term_file;
174
0
    }
175
0
  }
176
177
0
  struct termios orig_flags = WINPR_C_ARRAY_INIT;
178
0
  if (tcgetattr(terminal_fildes, &orig_flags) != -1)
179
0
  {
180
0
    struct termios new_flags = WINPR_C_ARRAY_INIT;
181
0
    new_flags = orig_flags;
182
0
    new_flags.c_lflag &= (uint32_t)~ECHO;
183
0
    new_flags.c_lflag |= ECHONL;
184
0
    terminal_needs_reset = TRUE;
185
0
    if (tcsetattr(terminal_fildes, TCSAFLUSH, &new_flags) == -1)
186
0
      terminal_needs_reset = FALSE;
187
0
  }
188
189
0
  FILE* fp = fdopen(terminal_fildes, "r");
190
0
  if (!fp)
191
0
    goto error;
192
193
0
  (void)fprintf(fout, "%s", prompt);
194
0
  (void)fflush(fout);
195
196
0
  {
197
0
    char* ptr = nullptr;
198
0
    size_t ptr_len = 0;
199
0
    const SSIZE_T res = freerdp_interruptible_get_line(context, &ptr, &ptr_len, fp);
200
0
    if (res < 0)
201
0
      goto error;
202
203
0
    replace_char(ptr, ptr_len, "\r\n");
204
205
0
    strncpy(buf, ptr, MIN(bufsiz, ptr_len));
206
0
    free(ptr);
207
0
  }
208
209
0
  if (terminal_needs_reset)
210
0
  {
211
0
    if (tcsetattr(terminal_fildes, TCSAFLUSH, &orig_flags) == -1)
212
0
      goto error;
213
0
  }
214
215
0
  if (terminal_fildes != STDIN_FILENO)
216
0
    (void)fclose(fp);
217
218
0
  return buf;
219
220
0
error:
221
0
{
222
  // NOLINTNEXTLINE(clang-analyzer-unix.Stream)
223
0
  int saved_errno = errno;
224
0
  if (terminal_needs_reset)
225
0
    (void)tcsetattr(terminal_fildes, TCSAFLUSH, &orig_flags);
226
227
0
  if (terminal_fildes != STDIN_FILENO)
228
0
  {
229
0
    if (fp)
230
0
      (void)fclose(fp);
231
0
  }
232
  // NOLINTNEXTLINE(clang-analyzer-unix.Stream)
233
0
  errno = saved_errno;
234
0
}
235
236
0
  return nullptr;
237
0
}
238
239
static const char* freerdp_passphrase_read_askpass(const char* prompt, char* buf, size_t bufsiz,
240
                                                   char const* askpass_env)
241
0
{
242
0
  char command[4096] = WINPR_C_ARRAY_INIT;
243
244
0
  (void)sprintf_s(command, sizeof(command), "%s 'FreeRDP authentication\n%s'", askpass_env,
245
0
                  prompt);
246
  // NOLINTNEXTLINE(clang-analyzer-optin.taint.GenericTaint,bugprone-command-processor)
247
0
  FILE* askproc = popen(command, "r");
248
0
  if (!askproc)
249
0
    return nullptr;
250
0
  WINPR_ASSERT(bufsiz <= INT32_MAX);
251
0
  if (fgets(buf, (int)bufsiz, askproc) != nullptr)
252
0
    buf[strcspn(buf, "\r\n")] = '\0';
253
0
  else
254
0
    buf = nullptr;
255
0
  const int status = pclose(askproc);
256
0
  if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
257
0
    buf = nullptr;
258
259
0
  return buf;
260
0
}
261
262
const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf,
263
                                    size_t bufsiz, int from_stdin)
264
0
{
265
  // NOLINTNEXTLINE(concurrency-mt-unsafe)
266
0
  const char* askpass_env = getenv("FREERDP_ASKPASS");
267
268
0
  if (askpass_env)
269
0
    return freerdp_passphrase_read_askpass(prompt, buf, bufsiz, askpass_env);
270
0
  else
271
0
    return freerdp_passphrase_read_tty(context, prompt, buf, bufsiz, from_stdin);
272
0
}
273
274
static BOOL set_termianl_nonblock(int ifd, BOOL nonblock);
275
276
static void restore_terminal(void)
277
0
{
278
0
  (void)set_termianl_nonblock(-1, FALSE);
279
0
}
280
281
BOOL set_termianl_nonblock(int ifd, BOOL nonblock)
282
0
{
283
0
  static int fd = -1;
284
0
  static bool registered = false;
285
0
  static int orig = 0;
286
0
  static struct termios termios = WINPR_C_ARRAY_INIT;
287
288
0
  if (ifd >= 0)
289
0
    fd = ifd;
290
291
0
  if (fd < 0)
292
0
    return FALSE;
293
294
0
  if (nonblock)
295
0
  {
296
0
    if (!registered)
297
0
    {
298
0
      (void)winpr_atexit(restore_terminal);
299
0
      registered = true;
300
0
    }
301
302
0
    const int rc1 = fcntl(fd, F_SETFL, orig | O_NONBLOCK);
303
0
    if (rc1 != 0)
304
0
    {
305
0
      char buffer[128] = WINPR_C_ARRAY_INIT;
306
0
      WLog_ERR(TAG, "fcntl(F_SETFL) failed with %s",
307
0
               winpr_strerror(errno, buffer, sizeof(buffer)));
308
0
      return FALSE;
309
0
    }
310
0
    const int rc2 = tcgetattr(fd, &termios);
311
0
    if (rc2 != 0)
312
0
    {
313
0
      char buffer[128] = WINPR_C_ARRAY_INIT;
314
0
      WLog_ERR(TAG, "tcgetattr() failed with %s",
315
0
               winpr_strerror(errno, buffer, sizeof(buffer)));
316
0
      return FALSE;
317
0
    }
318
319
0
    struct termios now = termios;
320
0
    cfmakeraw(&now);
321
0
    const int rc3 = tcsetattr(fd, TCSANOW, &now);
322
0
    if (rc3 != 0)
323
0
    {
324
0
      char buffer[128] = WINPR_C_ARRAY_INIT;
325
0
      WLog_ERR(TAG, "tcsetattr(TCSANOW) failed with %s",
326
0
               winpr_strerror(errno, buffer, sizeof(buffer)));
327
0
      return FALSE;
328
0
    }
329
0
  }
330
0
  else
331
0
  {
332
0
    const int rc1 = tcsetattr(fd, TCSANOW, &termios);
333
0
    if (rc1 != 0)
334
0
    {
335
0
      char buffer[128] = WINPR_C_ARRAY_INIT;
336
0
      WLog_ERR(TAG, "tcsetattr(TCSANOW) failed with %s",
337
0
               winpr_strerror(errno, buffer, sizeof(buffer)));
338
0
      return FALSE;
339
0
    }
340
0
    const int rc2 = fcntl(fd, F_SETFL, orig);
341
0
    if (rc2 != 0)
342
0
    {
343
0
      char buffer[128] = WINPR_C_ARRAY_INIT;
344
0
      WLog_ERR(TAG, "fcntl(F_SETFL) failed with %s",
345
0
               winpr_strerror(errno, buffer, sizeof(buffer)));
346
0
      return FALSE;
347
0
    }
348
0
    fd = -1;
349
0
  }
350
0
  return TRUE;
351
0
}
352
353
int freerdp_interruptible_getc(rdpContext* context, FILE* stream)
354
0
{
355
0
  int rc = EOF;
356
0
  const int fd = fileno(stream);
357
358
0
  (void)set_termianl_nonblock(fd, TRUE);
359
360
0
  do
361
0
  {
362
0
    const int res = wait_for_fd(fd, 10);
363
0
    if (res != 0)
364
0
    {
365
0
      char c = 0;
366
0
      const ssize_t rd = read(fd, &c, 1);
367
0
      if (rd == 1)
368
0
      {
369
0
        if (c == 3) /* ctrl + c */
370
0
          return EOF;
371
0
        if (c == 4) /* ctrl + d */
372
0
          return EOF;
373
0
        if (c == 26) /* ctrl + z */
374
0
          return EOF;
375
0
        rc = (int)c;
376
0
      }
377
0
      break;
378
0
    }
379
0
  } while (!freerdp_shall_disconnect_context(context));
380
381
0
  (void)set_termianl_nonblock(fd, FALSE);
382
383
0
  return rc;
384
0
}
385
386
#else
387
388
const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf,
389
                                    size_t bufsiz, int from_stdin)
390
{
391
  return nullptr;
392
}
393
394
int freerdp_interruptible_getc(rdpContext* context, FILE* f)
395
{
396
  return EOF;
397
}
398
#endif
399
400
SSIZE_T freerdp_interruptible_get_line(rdpContext* context, char** plineptr, size_t* psize,
401
                                       FILE* stream)
402
0
{
403
0
  int c = 0;
404
0
  char* n = nullptr;
405
0
  size_t step = 32;
406
0
  size_t used = 0;
407
0
  char* ptr = nullptr;
408
0
  size_t len = 0;
409
410
0
  if (!plineptr || !psize)
411
0
  {
412
0
    errno = EINVAL;
413
0
    return -1;
414
0
  }
415
416
0
  bool echo = true;
417
0
#if !defined(_WIN32) && !defined(ANDROID)
418
0
  {
419
0
    const int fd = fileno(stream);
420
421
0
    struct termios termios = WINPR_C_ARRAY_INIT;
422
    /* This might fail if /from-stdin is used. */
423
0
    if (tcgetattr(fd, &termios) == 0)
424
0
      echo = (termios.c_lflag & ECHO) != 0;
425
0
    else
426
0
      echo = false;
427
0
  }
428
0
#endif
429
430
0
  if (*plineptr && (*psize > 0))
431
0
  {
432
0
    ptr = *plineptr;
433
0
    used = *psize;
434
0
    if (echo)
435
0
    {
436
0
      printf("%s", ptr);
437
0
      (void)fflush(stdout);
438
0
    }
439
0
  }
440
441
0
  do
442
0
  {
443
0
    if (used + 2 >= len)
444
0
    {
445
0
      len += step;
446
0
      n = realloc(ptr, len);
447
448
0
      if (!n)
449
0
      {
450
0
        free(ptr);
451
0
        *plineptr = nullptr;
452
0
        return -1;
453
0
      }
454
455
0
      ptr = n;
456
0
    }
457
458
0
    c = freerdp_interruptible_getc(context, stream);
459
0
    if (c == 127)
460
0
    {
461
0
      if (used > 0)
462
0
      {
463
0
        ptr[used--] = '\0';
464
0
        if (echo)
465
0
        {
466
0
          printf("\b");
467
0
          printf(" ");
468
0
          printf("\b");
469
0
          (void)fflush(stdout);
470
0
        }
471
0
      }
472
0
      continue;
473
0
    }
474
0
    if (echo)
475
0
    {
476
0
      printf("%c", c);
477
0
      (void)fflush(stdout);
478
0
    }
479
0
    if (c != EOF)
480
0
      ptr[used++] = (char)c;
481
0
  } while ((c != '\n') && (c != '\r') && (c != EOF));
482
483
0
  printf("\n");
484
0
  ptr[used] = '\0';
485
0
  if (c == EOF)
486
0
  {
487
0
    free(ptr);
488
0
    *plineptr = nullptr;
489
0
    return EOF;
490
0
  }
491
0
  *plineptr = ptr;
492
0
  *psize = used;
493
0
  return WINPR_ASSERTING_INT_CAST(SSIZE_T, used);
494
0
}