From 7dd58ed31f1073c7ef913c0c6f258cc38f2cf301 Mon Sep 17 00:00:00 2001 From: Bryan Berns Date: Thu, 12 Dec 2019 17:33:05 -0500 Subject: [PATCH] Allow Use Of Non-ASCII Character In SSH Client Passwords (#322) * Allow Use Of Non-ASCII Character In SSH Client Passwords --- contrib/win32/win32compat/misc.c | 407 ++++++++++++++++--------------- 1 file changed, 212 insertions(+), 195 deletions(-) diff --git a/contrib/win32/win32compat/misc.c b/contrib/win32/win32compat/misc.c index 9693fa859..115d6cdf7 100644 --- a/contrib/win32/win32compat/misc.c +++ b/contrib/win32/win32compat/misc.c @@ -1202,7 +1202,9 @@ char * readpassphrase(const char *prompt, char *outBuf, size_t outBufLen, int flags) { int current_index = 0; - char ch; + int utf8_read = 0; + char utf8_char[4]; + wchar_t ch; wchar_t* wtmp = NULL; if (outBufLen == 0) { @@ -1210,7 +1212,7 @@ readpassphrase(const char *prompt, char *outBuf, size_t outBufLen, int flags) return NULL; } - while (_kbhit()) _getch(); + while (_kbhit()) _getwch(); wtmp = utf8_to_utf16(prompt); if (wtmp == NULL) @@ -1220,36 +1222,51 @@ readpassphrase(const char *prompt, char *outBuf, size_t outBufLen, int flags) free(wtmp); while (current_index < (int)outBufLen - 1) { - ch = _getch(); + ch = _getwch(); - if (ch == '\r') { - if (_kbhit()) _getch(); /* read linefeed if its there */ + if (ch == L'\r') { + if (_kbhit()) _getwch(); /* read linefeed if its there */ break; - } else if (ch == '\n') { + } else if (ch == L'\n') { break; - } else if (ch == '\b') { /* backspace */ + } else if (ch == L'\b') { /* backspace */ if (current_index > 0) { if (flags & RPP_ECHO_ON) - printf_s("%c \b", ch); + wprintf_s(L"%c \b", ch); - current_index--; /* overwrite last character */ + /* overwrite last character - remove any utf8 extended chars */ + while (current_index > 0 && (outBuf[current_index - 1] & 0xC0) == 0x80) + current_index--; + + /* overwrite last character - remove first utf8 byte */ + if (current_index > 0) + current_index--; } - } else if (ch == '\003') { /* exit on Ctrl+C */ + } else if (ch == L'\003') { /* exit on Ctrl+C */ fatal(""); } else { if (flags & RPP_SEVENBIT) ch &= 0x7f; - if (isalpha((unsigned char)ch)) { + if (iswalpha(ch)) { if(flags & RPP_FORCELOWER) - ch = tolower((unsigned char)ch); + ch = towlower(ch); if(flags & RPP_FORCEUPPER) - ch = toupper((unsigned char)ch); + ch = towupper(ch); } - outBuf[current_index++] = ch; + /* convert unicode to utf8 characters */ + int utf8_char_size = sizeof(utf8_char); + if ((utf8_read = WideCharToMultiByte(CP_UTF8, 0, &ch, 1, utf8_char, sizeof(utf8_char), NULL, NULL)) == 0) + fatal("character conversion failed"); + + /* append to output buffer if the characters fit */ + if (current_index + utf8_read >= outBufLen - 1) break; + memcpy(&outBuf[current_index], utf8_char, utf8_read); + current_index += utf8_read; + if(flags & RPP_ECHO_ON) - printf_s("%c", ch); + wprintf_s(L"%c", ch); } } @@ -1710,186 +1727,186 @@ build_exec_command(const char * command) return cmd_sp; } -/* -* cmd is internally decoarated with a set of '"' -* to account for any spaces within the commandline -* the double quotes and backslash is escaped if needed -* this decoration is done only when additional arguments are passed in argv -*/ -char * -build_commandline_string(const char* cmd, char *const argv[], BOOLEAN prepend_module_path) -{ - char *cmdline, *t, *tmp = NULL, *path = NULL, *ret = NULL; - char * const *t1; - DWORD cmdline_len = 0, path_len = 0; - int add_module_path = 0; - - if (!cmd) { - error("%s invalid argument cmd:%s", __func__, cmd); - return NULL; - } - - if (!(path = _strdup(cmd))) { - error("failed to duplicate %s", cmd); - return NULL; - } - - path_len = (DWORD)strlen(path); - - if (is_bash_test_env()) { - memset(path, 0, path_len + 1); - bash_to_win_path(cmd, path, path_len + 1); - path_len = (DWORD)strlen(path); - } - - if (!is_absolute_path(path) && prepend_module_path) - add_module_path = 1; - - /* compute total cmdline len*/ - if (add_module_path) - cmdline_len += (DWORD)strlen(__progdir) + 1 + (DWORD)strlen(path) + 1 + 2; - else - cmdline_len += (DWORD)strlen(path) + 1 + 2; - - if (argv) { - t1 = argv; - while (*t1) { - char *p = *t1++; - for (int i = 0; i < (int)strlen(p); i++) { - if (p[i] == '\\') { - char * b = p + i; - int additional_backslash = 0; - int backslash_count = 0; - /* - Backslashes are interpreted literally, unless they immediately - precede a double quotation mark. - */ - while (b != NULL && *b == '\\') { - backslash_count++; - b++; - if (b != NULL && *b == '\"') { - additional_backslash = 1; - break; - } - } - cmdline_len += backslash_count * (additional_backslash + 1); - i += backslash_count - 1; - } - else if (p[i] == '\"') - /* backslash will be added for every double quote.*/ - cmdline_len += 2; - else - cmdline_len++; - } - cmdline_len += 1 + 2; /*for "around cmd arg and traling space*/ - } - } - - if ((cmdline = malloc(cmdline_len)) == NULL) { - errno = ENOMEM; - goto cleanup; - } - - t = cmdline; - - *t++ = '\"'; - if (add_module_path) { - /* add current module path to start if needed */ - memcpy(t, __progdir, strlen(__progdir)); - t += strlen(__progdir); - *t++ = '\\'; - } - - if (path[0] != '\"') { - /* If path is then we should add double quotes after i.e., "" should be passed to CreateProcess(). - * Example - If path is C:\cygwin64\bin\bash.exe /cygdrive/e/openssh-portable-latestw_all/openssh-portable/regress/scp-ssh-wrapper.sh then - * we should pass "C:\cygwin64\bin\bash.exe" /cygdrive/e/openssh-portable-latestw_all/openssh-portable/regress/scp-ssh-wrapper.sh - * to the CreateProcess() otherwise CreateProcess() will fail with error code 2. - */ - if (strstr(path, ".exe") && (tmp = strstr(strstr(path, ".exe"), " "))) - { - size_t tmp_pos = tmp - path; - memcpy(t, path, tmp_pos); - t += tmp_pos; - *t++ = '\"'; - memcpy(t, tmp, strlen(path) - tmp_pos); - t += (strlen(path) - tmp_pos); - } - else { - memcpy(t, path, path_len); - t += path_len; - *t++ = '\"'; - } - } - else { - /*path already contains "*/ - memcpy(t, path + 1, path_len - 1); - t += path_len - 1; - } - - *t = '\0'; - t = cmdline + strlen(cmdline); - - if (argv) { - t1 = argv; - while (*t1) { - *t++ = ' '; - char * p1 = *t1++; - BOOL add_quotes = FALSE; - /* leave as is if the command is surrounded by single quotes*/ - if (p1[0] != '\'') - for (int i = 0; i < (int)strlen(p1); i++) { - if (p1[i] == ' ') { - add_quotes = TRUE; - break; - } - } - if (add_quotes) - *t++ = '\"'; - for (int i = 0; i < (int)strlen(p1); i++) { - if (p1[i] == '\\') { - char * b = p1 + i; - int additional_backslash = 0; - int backslash_count = 0; - /* - * Backslashes are interpreted literally, unless they immediately - * precede a double quotation mark. - */ - while (b != NULL && *b == '\\') { - backslash_count++; - b++; - if (b != NULL && *b == '\"') { - additional_backslash = 1; - break; - } - } - i += backslash_count - 1; - int escaped_backslash_count = backslash_count * (additional_backslash + 1); - while (escaped_backslash_count--) - *t++ = '\\'; - } - else if (p1[i] == '\"') { - /* Add backslash for every double quote.*/ - *t++ = '\\'; - *t++ = '\"'; - } - else - *t++ = p1[i]; - } - if (add_quotes) - *t++ = '\"'; - } - } - *t = '\0'; - ret = cmdline; - cmdline = NULL; -cleanup: - if (path) - free(path); - if (cmdline) - free(cmdline); - return ret; -} +/* +* cmd is internally decoarated with a set of '"' +* to account for any spaces within the commandline +* the double quotes and backslash is escaped if needed +* this decoration is done only when additional arguments are passed in argv +*/ +char * +build_commandline_string(const char* cmd, char *const argv[], BOOLEAN prepend_module_path) +{ + char *cmdline, *t, *tmp = NULL, *path = NULL, *ret = NULL; + char * const *t1; + DWORD cmdline_len = 0, path_len = 0; + int add_module_path = 0; + + if (!cmd) { + error("%s invalid argument cmd:%s", __func__, cmd); + return NULL; + } + + if (!(path = _strdup(cmd))) { + error("failed to duplicate %s", cmd); + return NULL; + } + + path_len = (DWORD)strlen(path); + + if (is_bash_test_env()) { + memset(path, 0, path_len + 1); + bash_to_win_path(cmd, path, path_len + 1); + path_len = (DWORD)strlen(path); + } + + if (!is_absolute_path(path) && prepend_module_path) + add_module_path = 1; + + /* compute total cmdline len*/ + if (add_module_path) + cmdline_len += (DWORD)strlen(__progdir) + 1 + (DWORD)strlen(path) + 1 + 2; + else + cmdline_len += (DWORD)strlen(path) + 1 + 2; + + if (argv) { + t1 = argv; + while (*t1) { + char *p = *t1++; + for (int i = 0; i < (int)strlen(p); i++) { + if (p[i] == '\\') { + char * b = p + i; + int additional_backslash = 0; + int backslash_count = 0; + /* + Backslashes are interpreted literally, unless they immediately + precede a double quotation mark. + */ + while (b != NULL && *b == '\\') { + backslash_count++; + b++; + if (b != NULL && *b == '\"') { + additional_backslash = 1; + break; + } + } + cmdline_len += backslash_count * (additional_backslash + 1); + i += backslash_count - 1; + } + else if (p[i] == '\"') + /* backslash will be added for every double quote.*/ + cmdline_len += 2; + else + cmdline_len++; + } + cmdline_len += 1 + 2; /*for "around cmd arg and traling space*/ + } + } + + if ((cmdline = malloc(cmdline_len)) == NULL) { + errno = ENOMEM; + goto cleanup; + } + + t = cmdline; + + *t++ = '\"'; + if (add_module_path) { + /* add current module path to start if needed */ + memcpy(t, __progdir, strlen(__progdir)); + t += strlen(__progdir); + *t++ = '\\'; + } + + if (path[0] != '\"') { + /* If path is then we should add double quotes after i.e., "" should be passed to CreateProcess(). + * Example - If path is C:\cygwin64\bin\bash.exe /cygdrive/e/openssh-portable-latestw_all/openssh-portable/regress/scp-ssh-wrapper.sh then + * we should pass "C:\cygwin64\bin\bash.exe" /cygdrive/e/openssh-portable-latestw_all/openssh-portable/regress/scp-ssh-wrapper.sh + * to the CreateProcess() otherwise CreateProcess() will fail with error code 2. + */ + if (strstr(path, ".exe") && (tmp = strstr(strstr(path, ".exe"), " "))) + { + size_t tmp_pos = tmp - path; + memcpy(t, path, tmp_pos); + t += tmp_pos; + *t++ = '\"'; + memcpy(t, tmp, strlen(path) - tmp_pos); + t += (strlen(path) - tmp_pos); + } + else { + memcpy(t, path, path_len); + t += path_len; + *t++ = '\"'; + } + } + else { + /*path already contains "*/ + memcpy(t, path + 1, path_len - 1); + t += path_len - 1; + } + + *t = '\0'; + t = cmdline + strlen(cmdline); + + if (argv) { + t1 = argv; + while (*t1) { + *t++ = ' '; + char * p1 = *t1++; + BOOL add_quotes = FALSE; + /* leave as is if the command is surrounded by single quotes*/ + if (p1[0] != '\'') + for (int i = 0; i < (int)strlen(p1); i++) { + if (p1[i] == ' ') { + add_quotes = TRUE; + break; + } + } + if (add_quotes) + *t++ = '\"'; + for (int i = 0; i < (int)strlen(p1); i++) { + if (p1[i] == '\\') { + char * b = p1 + i; + int additional_backslash = 0; + int backslash_count = 0; + /* + * Backslashes are interpreted literally, unless they immediately + * precede a double quotation mark. + */ + while (b != NULL && *b == '\\') { + backslash_count++; + b++; + if (b != NULL && *b == '\"') { + additional_backslash = 1; + break; + } + } + i += backslash_count - 1; + int escaped_backslash_count = backslash_count * (additional_backslash + 1); + while (escaped_backslash_count--) + *t++ = '\\'; + } + else if (p1[i] == '\"') { + /* Add backslash for every double quote.*/ + *t++ = '\\'; + *t++ = '\"'; + } + else + *t++ = p1[i]; + } + if (add_quotes) + *t++ = '\"'; + } + } + *t = '\0'; + ret = cmdline; + cmdline = NULL; +cleanup: + if (path) + free(path); + if (cmdline) + free(cmdline); + return ret; +} BOOL is_bash_test_env()