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 (void)fprintf(fout, "%s", prompt);
258 (void)fflush(fout);
259
260 {
261 char* ptr = nullptr;
262 size_t ptr_len = 0;
263 const SSIZE_T res = freerdp_interruptible_get_line(context, &ptr, &ptr_len, fp);
264 if (res < 0)
265 goto error;
266
267 replace_char(ptr, ptr_len, "\r\n");
268
269 strncpy(buf, ptr, MIN(bufsiz, ptr_len));
270 free(ptr);
271 }
272
273 if (terminal_needs_reset)
274 {
275 if (tcsetattr(terminal_fildes, TCSAFLUSH, &orig_flags) == -1)
276 goto error;
277 }
278
279 if (terminal_fildes != STDIN_FILENO)
280 (void)fclose(fp);
281
282 return buf;
283
284error:
285{
286 // NOLINTNEXTLINE(clang-analyzer-unix.Stream)
287 int saved_errno = errno;
288 if (terminal_needs_reset)
289 (void)tcsetattr(terminal_fildes, TCSAFLUSH, &orig_flags);
290
291 if (terminal_fildes != STDIN_FILENO)
292 {
293 if (fp)
294 (void)fclose(fp);
295 }
296 // NOLINTNEXTLINE(clang-analyzer-unix.Stream)
297 errno = saved_errno;
298}
299
300 return nullptr;
301}
302
303static const char* freerdp_passphrase_read_askpass(const char* prompt, char* buf, size_t bufsiz,
304 char const* askpass_env)
305{
306 char command[4096] = WINPR_C_ARRAY_INIT;
307
308 (void)sprintf_s(command, sizeof(command), "%s 'FreeRDP authentication\n%s'", askpass_env,
309 prompt);
310 // NOLINTNEXTLINE(clang-analyzer-optin.taint.GenericTaint,bugprone-command-processor)
311 FILE* askproc = popen(command, "r");
312 if (!askproc)
313 return nullptr;
314 WINPR_ASSERT(bufsiz <= INT32_MAX);
315 if (fgets(buf, (int)bufsiz, askproc) != nullptr)
316 buf[strcspn(buf, "\r\n")] = '\0';
317 else
318 buf = nullptr;
319 const int status = pclose(askproc);
320 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
321 buf = nullptr;
322
323 return buf;
324}
325
326const char* freerdp_passphrase_from_env(WINPR_ATTR_UNUSED rdpContext* context, const char* prompt,
327 char* buf, size_t bufsiz)
328{
329 // NOLINTNEXTLINE(concurrency-mt-unsafe)
330 const char* askpass_env = getenv("FREERDP_ASKPASS");
331 if (!askpass_env)
332 return nullptr;
333 return freerdp_passphrase_read_askpass(prompt, buf, bufsiz, askpass_env);
334}
335
336const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf,
337 size_t bufsiz, int from_stdin)
338{
339 const char* askpass_env = freerdp_passphrase_from_env(context, prompt, buf, bufsiz);
340 if (askpass_env)
341 return askpass_env;
342
343 return freerdp_passphrase_read_tty(context, prompt, buf, bufsiz, from_stdin);
344}
345
346static BOOL set_termianl_nonblock(int ifd, BOOL nonblock);
347
348static void restore_terminal(void)
349{
350 (void)set_termianl_nonblock(-1, FALSE);
351}
352
353BOOL set_termianl_nonblock(int ifd, BOOL nonblock)
354{
355 static int fd = -1;
356 static bool registered = false;
357 static int orig = 0;
358 static struct termios termios = WINPR_C_ARRAY_INIT;
359
360 if (ifd >= 0)
361 fd = ifd;
362
363 if (fd < 0)
364 return FALSE;
365
366 if (nonblock)
367 {
368 if (!registered)
369 {
370 (void)winpr_atexit(restore_terminal);
371 registered = true;
372 }
373
374 const int rc1 = fcntl(fd, F_SETFL, orig | O_NONBLOCK);
375 if (rc1 != 0)
376 {
377 char buffer[128] = WINPR_C_ARRAY_INIT;
378 WLog_ERR(TAG, "fcntl(F_SETFL) failed with %s",
379 winpr_strerror(errno, buffer, sizeof(buffer)));
380 return FALSE;
381 }
382 const int rc2 = tcgetattr(fd, &termios);
383 if (rc2 != 0)
384 {
385 char buffer[128] = WINPR_C_ARRAY_INIT;
386 WLog_ERR(TAG, "tcgetattr() failed with %s",
387 winpr_strerror(errno, buffer, sizeof(buffer)));
388 return FALSE;
389 }
390
391 struct termios now = termios;
392 cfmakeraw(&now);
393 const int rc3 = tcsetattr(fd, TCSANOW, &now);
394 if (rc3 != 0)
395 {
396 char buffer[128] = WINPR_C_ARRAY_INIT;
397 WLog_ERR(TAG, "tcsetattr(TCSANOW) failed with %s",
398 winpr_strerror(errno, buffer, sizeof(buffer)));
399 return FALSE;
400 }
401 }
402 else
403 {
404 const int rc1 = tcsetattr(fd, TCSANOW, &termios);
405 if (rc1 != 0)
406 {
407 char buffer[128] = WINPR_C_ARRAY_INIT;
408 WLog_ERR(TAG, "tcsetattr(TCSANOW) failed with %s",
409 winpr_strerror(errno, buffer, sizeof(buffer)));
410 return FALSE;
411 }
412 const int rc2 = fcntl(fd, F_SETFL, orig);
413 if (rc2 != 0)
414 {
415 char buffer[128] = WINPR_C_ARRAY_INIT;
416 WLog_ERR(TAG, "fcntl(F_SETFL) failed with %s",
417 winpr_strerror(errno, buffer, sizeof(buffer)));
418 return FALSE;
419 }
420 fd = -1;
421 }
422 return TRUE;
423}
424
425int freerdp_interruptible_getc(rdpContext* context, FILE* stream)
426{
427 int rc = EOF;
428 const int fd = fileno(stream);
429
430 (void)set_termianl_nonblock(fd, TRUE);
431
432 do
433 {
434 const int res = wait_for_fd(fd, 10);
435 if (res != 0)
436 {
437 char c = 0;
438 const ssize_t rd = read(fd, &c, 1);
439 if (rd == 1)
440 {
441 if (c == 3) /* ctrl + c */
442 return EOF;
443 if (c == 4) /* ctrl + d */
444 return EOF;
445 if (c == 26) /* ctrl + z */
446 return EOF;
447 rc = (int)c;
448 }
449 break;
450 }
451 } while (!freerdp_shall_disconnect_context(context));
452
453 (void)set_termianl_nonblock(fd, FALSE);
454
455 return rc;
456}
457
458#else
459
460const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf,
461 size_t bufsiz, int from_stdin)
462{
463 return nullptr;
464}
465
466int freerdp_interruptible_getc(rdpContext* context, FILE* f)
467{
468 return EOF;
469}
470
471const 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
479const 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
489SSIZE_T freerdp_interruptible_get_line(rdpContext* context, char** plineptr, size_t* psize,
490 FILE* stream)
491{
492 int c = 0;
493 char* n = nullptr;
494 size_t step = 32;
495 size_t used = 0;
496 char* ptr = nullptr;
497 size_t len = 0;
498
499 if (!plineptr || !psize)
500 {
501 errno = EINVAL;
502 return -1;
503 }
504
505 bool echo = true;
506#if !defined(_WIN32) && !defined(ANDROID)
507 {
508 const int fd = fileno(stream);
509
510 struct termios termios = WINPR_C_ARRAY_INIT;
511 /* This might fail if /from-stdin is used. */
512 if (tcgetattr(fd, &termios) == 0)
513 echo = (termios.c_lflag & ECHO) != 0;
514 else
515 echo = false;
516 }
517#endif
518
519 if (*plineptr && (*psize > 0))
520 {
521 ptr = *plineptr;
522 used = *psize;
523 if (echo)
524 {
525 printf("%s", ptr);
526 (void)fflush(stdout);
527 }
528 }
529
530 do
531 {
532 if (used + 2 >= len)
533 {
534 len += step;
535 n = realloc(ptr, len);
536
537 if (!n)
538 {
539 free(ptr);
540 *plineptr = nullptr;
541 return -1;
542 }
543
544 ptr = n;
545 }
546
547 c = freerdp_interruptible_getc(context, stream);
548 if (c == 127)
549 {
550 if (used > 0)
551 {
552 ptr[used--] = '\0';
553 if (echo)
554 {
555 printf("\b");
556 printf(" ");
557 printf("\b");
558 (void)fflush(stdout);
559 }
560 }
561 continue;
562 }
563 if (echo)
564 {
565 printf("%c", c);
566 (void)fflush(stdout);
567 }
568 if (c != EOF)
569 ptr[used++] = (char)c;
570 } while ((c != '\n') && (c != '\r') && (c != EOF));
571
572 printf("\n");
573 ptr[used] = '\0';
574 if (c == EOF)
575 {
576 free(ptr);
577 *plineptr = nullptr;
578 return EOF;
579 }
580 *plineptr = ptr;
581 *psize = used;
582 return WINPR_ASSERTING_INT_CAST(SSIZE_T, used);
583}