From 1ada469cdd59b749ddc65f08455f227a55c3d1ab Mon Sep 17 00:00:00 2001 From: akallabeth Date: Fri, 26 Sep 2025 13:26:20 +0200 Subject: [PATCH] [freerdp,utils] modify freerdp_interruptible_get_line * Make freerdp_interruptible_getc nonblocking so each typed char is returned * Handle ctrl+[cdz] in freerdp_interruptible_getc so the function can be aborted by a user * Handle simple input edit in freerdp_interruptible_get_line (delete characters, provide a suggested value) --- include/freerdp/utils/passphrase.h | 39 +++++++- libfreerdp/utils/passphrase.c | 147 +++++++++++++++++++++++++++-- 2 files changed, 179 insertions(+), 7 deletions(-) diff --git a/include/freerdp/utils/passphrase.h b/include/freerdp/utils/passphrase.h index 0bcce1953..0290d0df6 100644 --- a/include/freerdp/utils/passphrase.h +++ b/include/freerdp/utils/passphrase.h @@ -31,9 +31,46 @@ extern "C" { #endif - FREERDP_API int freerdp_interruptible_getc(rdpContext* context, FILE* file); + /** @brief Read a single character from a stream. + * This function will check \ref freerdp_shall_disconnect_context and abort when the session + * terminates. + * + * The function will abort on any of +[cdz] or end of stream conditions as well as when + * the RDP session terminates + * + * @param context The RDP context to work on + * @param stream the \ref FILE to read the data from + * + * @return The character read or \ref EOF in case of any failures + */ + FREERDP_API int freerdp_interruptible_getc(rdpContext* context, FILE* stream); + + /** @brief read a line from \ref stream with (optinal) default value that can be manipulated. + * This function will check \ref freerdp_shall_disconnect_context and abort when the session + * terminates. + * + * @param context The RDP context to work on + * @param lineptr on input a suggested default value, on output the result. Must be an allocated + * string on input, free the memory later with \ref free + * @param size on input the \ref strlen of the suggested default, on output \ref strlen of the + * result + * @param stream the \ref FILE to read the data from + * + * @return \b -1 in case of failure, otherwise \ref strlen of the result + */ FREERDP_API SSIZE_T freerdp_interruptible_get_line(rdpContext* context, char** lineptr, size_t* size, FILE* stream); + + /** @brief similar to \ref freerdp_interruptible_get_line but disables echo to terminal. + * + * @param context The RDP context to work on + * @param prompt The prompt to show to the user + * @param buf A pointer to a buffer that will receive the output + * @param bufsiz The size of the buffer in bytes + * @param from_stdin + * + * @return A pointer to \ref buf containing the password or \ref NULL in case of an error. + */ FREERDP_API const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, char* buf, size_t bufsiz, int from_stdin); diff --git a/libfreerdp/utils/passphrase.c b/libfreerdp/utils/passphrase.c index fe6a58119..a072f1a39 100644 --- a/libfreerdp/utils/passphrase.c +++ b/libfreerdp/utils/passphrase.c @@ -76,7 +76,7 @@ const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, cha #include #include #include - +#include #if defined(WINPR_HAVE_POLL_H) && !defined(__APPLE__) #include #else @@ -84,6 +84,8 @@ const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, cha #include #endif +#define TAG FREERDP_TAG("utils.passphrase") + static int wait_for_fd(int fd, int timeout) { int status = 0; @@ -265,13 +267,92 @@ const char* freerdp_passphrase_read(rdpContext* context, const char* prompt, cha return freerdp_passphrase_read_tty(context, prompt, buf, bufsiz, from_stdin); } -int freerdp_interruptible_getc(rdpContext* context, FILE* f) +static BOOL set_termianl_nonblock(int ifd, BOOL nonblock); + +static void restore_terminal(void) +{ + (void)set_termianl_nonblock(-1, FALSE); +} + +BOOL set_termianl_nonblock(int ifd, BOOL nonblock) +{ + static int fd = -1; + static bool registered = false; + static int orig = 0; + static struct termios termios = { 0 }; + + if (ifd >= 0) + fd = ifd; + + if (fd < 0) + return FALSE; + + if (nonblock) + { + if (!registered) + { + (void)atexit(restore_terminal); + registered = true; + } + + const int rc1 = fcntl(fd, F_SETFL, orig | O_NONBLOCK); + if (rc1 != 0) + { + char buffer[128] = { 0 }; + WLog_ERR(TAG, "fcntl(F_SETFL) failed with %s", + winpr_strerror(errno, buffer, sizeof(buffer))); + return FALSE; + } + const int rc2 = tcgetattr(fd, &termios); + if (rc2 != 0) + { + char buffer[128] = { 0 }; + WLog_ERR(TAG, "tcgetattr() failed with %s", + winpr_strerror(errno, buffer, sizeof(buffer))); + return FALSE; + } + + struct termios now = termios; + cfmakeraw(&now); + const int rc3 = tcsetattr(fd, TCSANOW, &now); + if (rc3 != 0) + { + char buffer[128] = { 0 }; + WLog_ERR(TAG, "tcsetattr(TCSANOW) failed with %s", + winpr_strerror(errno, buffer, sizeof(buffer))); + return FALSE; + } + } + else + { + const int rc1 = tcsetattr(fd, TCSANOW, &termios); + if (rc1 != 0) + { + char buffer[128] = { 0 }; + WLog_ERR(TAG, "tcsetattr(TCSANOW) failed with %s", + winpr_strerror(errno, buffer, sizeof(buffer))); + return FALSE; + } + const int rc2 = fcntl(fd, F_SETFL, orig); + if (rc2 != 0) + { + char buffer[128] = { 0 }; + WLog_ERR(TAG, "fcntl(F_SETFL) failed with %s", + winpr_strerror(errno, buffer, sizeof(buffer))); + return FALSE; + } + fd = -1; + } + return TRUE; +} + +int freerdp_interruptible_getc(rdpContext* context, FILE* stream) { int rc = EOF; - const int fd = fileno(f); + const int fd = fileno(stream); - const int orig = fcntl(fd, F_GETFL); - (void)fcntl(fd, F_SETFL, orig | O_NONBLOCK); + if (!set_termianl_nonblock(fd, TRUE)) + return EOF; do { const int res = wait_for_fd(fd, 10); @@ -280,12 +361,22 @@ int freerdp_interruptible_getc(rdpContext* context, FILE* f) char c = 0; const ssize_t rd = read(fd, &c, 1); if (rd == 1) + { + if (c == 3) /* ctrl + c */ + return EOF; + if (c == 4) /* ctrl + d */ + return EOF; + if (c == 26) /* ctrl + z */ + return EOF; rc = (int)c; + } break; } } while (!freerdp_shall_disconnect_context(context)); - (void)fcntl(fd, F_SETFL, orig); + if (!set_termianl_nonblock(fd, FALSE)) + return EOF; + return rc; } @@ -319,6 +410,29 @@ SSIZE_T freerdp_interruptible_get_line(rdpContext* context, char** plineptr, siz return -1; } + bool echo = true; +#if !defined(_WIN32) && !defined(ANDROID) + { + const int fd = fileno(stream); + + struct termios termios = { 0 }; + if (tcgetattr(fd, &termios) != 0) + return -1; + echo = (termios.c_lflag & ECHO) != 0; + } +#endif + + if (*plineptr && (*psize > 0)) + { + ptr = *plineptr; + used = *psize; + if (echo) + { + printf("%s", ptr); + (void)fflush(stdout); + } + } + do { if (used + 2 >= len) @@ -335,10 +449,31 @@ SSIZE_T freerdp_interruptible_get_line(rdpContext* context, char** plineptr, siz } c = freerdp_interruptible_getc(context, stream); + if (c == 127) + { + if (used > 0) + { + ptr[used--] = '\0'; + if (echo) + { + printf("\b"); + printf(" "); + printf("\b"); + (void)fflush(stdout); + } + } + continue; + } + if (echo) + { + printf("%c", c); + (void)fflush(stdout); + } if (c != EOF) ptr[used++] = (char)c; } while ((c != '\n') && (c != '\r') && (c != EOF)); + printf("\n"); ptr[used] = '\0'; if (c == EOF) {