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