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