Coverage Report

Created: 2025-10-10 06:50

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