From a75116b6f8b884051243556f2f0fcbbc37bd907d Mon Sep 17 00:00:00 2001 From: Yanbing Date: Mon, 5 Nov 2018 12:22:20 -0800 Subject: [PATCH] Fix of 1211 and 1082 (#349) PowerShell/Win32-OpenSSH#1211 PowerShell/Win32-OpenSSH#1082 Added support for posix_spawnp that executes the command directly instead of appending path. (SH_ASKPASS and proxy command use this). Refactored posix spawn commandline building logic to automatically account for Windows CRT escaping rules on all arguments. --- contrib/win32/openssh/sshd.vcxproj | 3 +- contrib/win32/openssh/sshd.vcxproj.filters | 8 +- contrib/win32/win32compat/misc.c | 408 ++++++++-------- contrib/win32/win32compat/misc_internal.h | 5 +- contrib/win32/win32compat/pwd.c | 5 + contrib/win32/win32compat/spawn-ext.c | 4 +- contrib/win32/win32compat/w32-doexec.c | 447 ++++++++++++++++++ contrib/win32/win32compat/w32fd.c | 122 +---- contrib/win32/win32compat/win32_pty.c | 43 +- contrib/win32/win32compat/wmain_sshd.c | 36 +- pal_doexec.h | 8 + regress/pesterTests/SCP.Tests.ps1 | 8 +- regress/pesterTests/SSH.Tests.ps1 | 129 +++-- .../win32compat/miscellaneous_tests.c | 160 +++---- session.c | 368 +------------- 15 files changed, 982 insertions(+), 772 deletions(-) create mode 100644 contrib/win32/win32compat/w32-doexec.c create mode 100644 pal_doexec.h diff --git a/contrib/win32/openssh/sshd.vcxproj b/contrib/win32/openssh/sshd.vcxproj index 5362732aa..0736c53a8 100644 --- a/contrib/win32/openssh/sshd.vcxproj +++ b/contrib/win32/openssh/sshd.vcxproj @@ -462,7 +462,8 @@ - + + diff --git a/contrib/win32/openssh/sshd.vcxproj.filters b/contrib/win32/openssh/sshd.vcxproj.filters index 643ebb7b6..54f52a6d1 100644 --- a/contrib/win32/openssh/sshd.vcxproj.filters +++ b/contrib/win32/openssh/sshd.vcxproj.filters @@ -150,10 +150,16 @@ Source Files + + Source Files + + + Source Files + Resource Files - + diff --git a/contrib/win32/win32compat/misc.c b/contrib/win32/win32compat/misc.c index 5a83a642f..aab66e9ca 100644 --- a/contrib/win32/win32compat/misc.c +++ b/contrib/win32/win32compat/misc.c @@ -910,7 +910,7 @@ realpath(const char *inputpath, char resolved[PATH_MAX]) if (is_win_path) { if (_strnicmp(inputpath, PROGRAM_DATA, strlen(PROGRAM_DATA)) == 0) { - strcat_s(path, PATH_MAX, __progdata); + strcpy_s(path, PATH_MAX, __progdata); strcat_s(path, PATH_MAX, &inputpath[strlen(PROGRAM_DATA)]); } else { memcpy_s(path, PATH_MAX, inputpath, strlen(inputpath)); @@ -1318,7 +1318,7 @@ int is_absolute_path(const char *path) { int retVal = 0; - if(*path == '\"') /* skip double quote if path is "c:\abc" */ + if(*path == '\"' || *path == '\'') /* skip double quote if path is "c:\abc" */ path++; if (*path == '/' || *path == '\\' || (*path != '\0' && isalpha(*path) && path[1] == ':') || @@ -1583,201 +1583,222 @@ cleanup: return ret; } - -/* builds session commandline. returns NULL with errno set on failure, caller should free returned string */ -char* -build_session_commandline(const char *shell, const char* shell_arg, const char *command) +/* Interpret scp and sftp executables*/ +char * +build_exec_command(const char * command) { - enum sh_type { SH_OTHER, SH_CMD, SH_PS, SH_BASH, SH_CYGWIN } shell_type = SH_OTHER; enum cmd_type { CMD_OTHER, CMD_SFTP, CMD_SCP } command_type = CMD_OTHER; - char *progdir = __progdir, *cmd_sp = NULL, *cmdline = NULL, *ret = NULL, *p; - int len, progdir_len = (int)strlen(progdir); + char *cmd_sp = NULL; + int len = 0, command_len; + const char *command_args = NULL; -#define CMDLINE_APPEND(P, S) \ -do { \ - int _S_len = (int)strlen(S); \ - memcpy((P), (S), _S_len); \ - (P) += _S_len; \ -} while(0) + if (!command) + return NULL; - /* get shell type */ - if (strstr(shell, "system32\\cmd")) - shell_type = SH_CMD; - else if (strstr(shell, "powershell")) - shell_type = SH_PS; - else if (strstr(shell, "\\bash")) - shell_type = SH_BASH; - else if (strstr(shell, "cygwin")) { - shell_type = SH_CYGWIN; + command_len = (int)strlen(command); + /*TODO - replace numbers below with readable compile time operators*/ + if (command_len >= 13 && _memicmp(command, "internal-sftp", 13) == 0) { + command_type = CMD_SFTP; + command_args = command + 13; } + else if (command_len >= 11 && _memicmp(command, "sftp-server", 11) == 0) { + command_type = CMD_SFTP; - /* special case where incoming command needs to be adjusted */ - do { - /* - * identify scp and sftp sessions - * we want to launch scp and sftp executables from the same binary directory - * that sshd is hosted in. This will facilitate hosting and evaluating - * multiple versions of OpenSSH at the same time. - * - * currently we can only accomodate this for cmd.exe, since cmd.exe simply executes - * its commandline without applying CRT or shell specific rules - * - * Ex. - * this works - * cmd /c "c:\program files\sftp" -d - * this following wouldn't work in powershell, cygwin's or WSL bash unless we put - * some shell specific rules - * powershell -c "c:\program files\scp" -t - * cygwin\bash -c "c:\program files\scp" -t - * bash -c "c:\program files\scp" -t - * - * for now, for all non-cmd shells, we launch scp and sftp-server directly - - * shell -c "scp.exe -t" - * note that .exe extension and case matters for WSL bash - * note that double quotes matter for WSL and Cygwin bash, they dont matter for PS - * - * consequence - - * for non-cmd shells - sftp and scp installation path is expected to be in machine wide PATH - * - */ - - int command_len; - const char *command_args = NULL; - - if (!command) - break; - command_len = (int)strlen(command); - /*TODO - replace numbers below with readable compile time operators*/ - if (command_len >= 13 && _memicmp(command, "internal-sftp", 13) == 0) { - command_type = CMD_SFTP; - command_args = command + 13; - } else if (command_len >= 11 && _memicmp(command, "sftp-server", 11) == 0) { - command_type = CMD_SFTP; - - /* account for possible .exe extension */ - if (command_len >= 15 && _memicmp(command + 11, ".exe", 4) == 0) - command_args = command + 15; - else - command_args = command + 11; - } else if (command_len >= 3 && _memicmp(command, "scp", 3) == 0) { - command_type = CMD_SCP; - - /* account for possible .exe extension */ - if (command_len >= 7 && _memicmp(command + 3, ".exe", 4) == 0) - command_args = command + 7; - else - command_args = command + 3; - } - - if (command_type == CMD_OTHER) - break; - - len = 0; - len += progdir_len + 4; /* account for " around */ - len += command_len + 4; /* account for possible .exe addition */ - - if ((cmd_sp = malloc(len)) == NULL) { - errno = ENOMEM; - goto done; - } - - p = cmd_sp; - - if ((shell_type == SH_CMD) || (shell_type == SH_CYGWIN)) { - CMDLINE_APPEND(p, "\""); - CMDLINE_APPEND(p, progdir); - - if (command_type == CMD_SCP) - CMDLINE_APPEND(p, "\\scp.exe\""); - else - CMDLINE_APPEND(p, "\\sftp-server.exe\""); - } else { - if (command_type == CMD_SCP) - CMDLINE_APPEND(p, "scp.exe"); - else - CMDLINE_APPEND(p, "sftp-server.exe"); - } - - if (shell_type == SH_CYGWIN) { - *p = '\0'; - convertToForwardslash(cmd_sp); - } - - CMDLINE_APPEND(p, command_args); - *p = '\0'; - command = cmd_sp; - } while (0); - - len = 0; - len +=(int) strlen(shell) + 3;/* 3 for " around shell path and trailing space */ - if (command) { - len += 15; /* for shell command argument, typically -c or /c */ - - int extra_buffer_len = 0; - if (is_bash_test_env()) - extra_buffer_len = 50; /* 50 - To escape double quotes or backslash in command (Ex - yes-head.sh) */ - - len += (int)strlen(command) + 5 + extra_buffer_len; /* 5 for possible " around command and null term*/ - } - - if ((cmdline = malloc(len)) == NULL) { - errno = ENOMEM; - goto done; - } - - p = cmdline; - CMDLINE_APPEND(p, "\""); - CMDLINE_APPEND(p, shell); - CMDLINE_APPEND(p, "\""); - if (command) { - if (shell_arg) { - CMDLINE_APPEND(p, " "); - CMDLINE_APPEND(p, shell_arg); - CMDLINE_APPEND(p, " "); - } - else if (shell_type == SH_CMD) - CMDLINE_APPEND(p, " /c "); + /* account for possible .exe extension */ + if (command_len >= 15 && _memicmp(command + 11, ".exe", 4) == 0) + command_args = command + 15; else - CMDLINE_APPEND(p, " -c "); - - /* Add double quotes around command */ - CMDLINE_APPEND(p, "\""); - if (is_bash_test_env()) { - /* Escape the double quotes and backslash as per CRT rules. - * Right now this logic is applied only in bash test environment. - * TODO - verify if this logic is applicable to all the shells. - */ - for (int i = 0; i < strlen(command); i++) { - if (command[i] == '\\') { - CMDLINE_APPEND(p, "\\"); - CMDLINE_APPEND(p, "\\"); // For every backslash add another backslash. - } - else if (command[i] == '\"') { - CMDLINE_APPEND(p, "\\"); // Add backslash for every double quote. - CMDLINE_APPEND(p, "\""); - } - else { - *p++ = command[i]; - } - } - } else { - CMDLINE_APPEND(p, command); - } - - CMDLINE_APPEND(p, "\""); + command_args = command + 11; } - *p = '\0'; - ret = cmdline; - cmdline = NULL; -done: - if (cmd_sp) - free(cmd_sp); - if (cmdline) - free(cmdline); + else if (command_len >= 3 && _memicmp(command, "scp", 3) == 0) { + command_type = CMD_SCP; - return ret; + /* account for possible .exe extension */ + if (command_len >= 7 && _memicmp(command + 3, ".exe", 4) == 0) + command_args = command + 7; + else + command_args = command + 3; + } + + len = command_len + 5; /* account for possible .exe addition and null term */ + if ((cmd_sp = malloc(len)) == NULL) { + errno = ENOMEM; + return NULL; + } + memset(cmd_sp, '\0', len); + if (command_type == CMD_SCP) { + strcpy_s(cmd_sp, len, "scp.exe"); + strcat_s(cmd_sp, len, command_args); + } + else if (command_type == CMD_SFTP) { + strcpy_s(cmd_sp, len, "sftp-server.exe"); + strcat_s(cmd_sp, len, command_args); + } + else + strcpy_s(cmd_sp, len, 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); + } + + 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] != '\"') { + 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() { @@ -1800,15 +1821,14 @@ bash_to_win_path(const char *in, char *out, const size_t out_len) { int retVal = 0; const size_t cygwin_path_prefix_len = strlen(CYGWIN_PATH_PREFIX); + memset(out, 0, out_len); if (_strnicmp(in, CYGWIN_PATH_PREFIX, cygwin_path_prefix_len) == 0) { - memset(out, 0, out_len); out[0] = in[cygwin_path_prefix_len]; out[1] = ':'; strcat_s(out, out_len, &in[cygwin_path_prefix_len + 1]); retVal = 1; - } else { - strcat_s(out, out_len, in); - } + } else + strcpy_s(out, out_len, in); return retVal; } @@ -1838,7 +1858,7 @@ freerrset(struct rrsetinfo *rrset) return; } -void +void debug_assert_internal() { /* debug break on non-release builds */ @@ -1847,7 +1867,7 @@ debug_assert_internal() #endif } -char +char *crypt(const char *key, const char *salt) { verbose("%s is not supported", __func__); @@ -1855,11 +1875,11 @@ char return NULL; } -int +int w32_system(const char *command) { int ret = -1; - wchar_t *command_w = NULL; + wchar_t *command_w = NULL; if (!command) { errno = ENOTSUP; diff --git a/contrib/win32/win32compat/misc_internal.h b/contrib/win32/win32compat/misc_internal.h index bc4e5068b..0dc05b1de 100644 --- a/contrib/win32/win32compat/misc_internal.h +++ b/contrib/win32/win32compat/misc_internal.h @@ -70,9 +70,10 @@ int is_absolute_path(const char *); int file_in_chroot_jail(HANDLE); PSID get_sid(const char*); int am_system(); -char* build_session_commandline(const char *, const char *, const char *); int is_conpty_supported(); -int exec_command_with_pty(wchar_t*, STARTUPINFOW*, PROCESS_INFORMATION*, int); +int exec_command_with_pty(int * pid, char* cmd, int in, int out, int err, unsigned int col, unsigned int row, int ttyfd); +char * build_exec_command(const char * command); +char * build_commandline_string(const char* cmd, char *const argv[], BOOLEAN prepend_module_path); char* get_custom_lsa_package(); wchar_t* get_final_path_by_handle(HANDLE h); int lookup_principal_name(const wchar_t * sam_account_name, wchar_t * user_principal_name); diff --git a/contrib/win32/win32compat/pwd.c b/contrib/win32/win32compat/pwd.c index 5b4fb5eec..d528fe33c 100644 --- a/contrib/win32/win32compat/pwd.c +++ b/contrib/win32/win32compat/pwd.c @@ -50,6 +50,7 @@ static struct passwd pw; static char* pw_shellpath = NULL; char* shell_command_option = NULL; +BOOLEAN arg_escape = TRUE; /* returns 0 on success, and -1 with errno set on failure */ static int @@ -76,8 +77,12 @@ set_defaultshell() (path_buf[0] != L'\0')) { /* fetched default shell path from registry */ tmp_len = _countof(option_buf); + DWORD size = sizeof(DWORD); + DWORD escape_option = 1; if (RegQueryValueExW(reg_key, L"DefaultShellCommandOption", 0, NULL, (LPBYTE)option_buf, &tmp_len) != ERROR_SUCCESS) option_buf[0] = L'\0'; + if (RegQueryValueExW(reg_key, L"DefaultShellEscapeArguments", 0, NULL, (LPBYTE)&escape_option, &size) == ERROR_SUCCESS) + arg_escape = (escape_option != 0) ? TRUE : FALSE; } else { if (!GetSystemDirectoryW(path_buf, _countof(path_buf))) { errno = GetLastError(); diff --git a/contrib/win32/win32compat/spawn-ext.c b/contrib/win32/win32compat/spawn-ext.c index 80d6dc569..45de4ba9f 100644 --- a/contrib/win32/win32compat/spawn-ext.c +++ b/contrib/win32/win32compat/spawn-ext.c @@ -3,7 +3,7 @@ #include "inc\unistd.h" #include "debug.h" -int posix_spawn_internal(pid_t *pidp, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[], HANDLE user_token); +int posix_spawn_internal(pid_t *pidp, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[], HANDLE user_token, BOOLEAN prepend_module_path); int __posix_spawn_asuser(pid_t *pidp, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[], char* user) @@ -21,7 +21,7 @@ __posix_spawn_asuser(pid_t *pidp, const char *path, const posix_spawn_file_actio if (strcmp(user, "sshd")) load_user_profile(user_token, user); - r = posix_spawn_internal(pidp, path, file_actions, attrp, argv, envp, user_token); + r = posix_spawn_internal(pidp, path, file_actions, attrp, argv, envp, user_token, TRUE); CloseHandle(user_token); return r; } \ No newline at end of file diff --git a/contrib/win32/win32compat/w32-doexec.c b/contrib/win32/win32compat/w32-doexec.c new file mode 100644 index 000000000..5bd55fb1f --- /dev/null +++ b/contrib/win32/win32compat/w32-doexec.c @@ -0,0 +1,447 @@ +/* +* Author: Yanbing Wang +* +* Support execution of commands on Win32 based operating systems. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include "includes.h" + +#include +#include "xmalloc.h" +#include "packet.h" + +#include "channels.h" +#include "hostfile.h" +#include "auth.h" +#include "log.h" +#include "misc.h" +#include "servconf.h" +#include "pal_doexec.h" +#include "misc_internal.h" + +#ifndef SUBSYSTEM_NONE +#define SUBSYSTEM_NONE 0 +#endif +#ifndef SUBSYSTEM_EXT +#define SUBSYSTEM_EXT 1 +#endif +#ifndef SUBSYSTEM_INT_SFTP +#define SUBSYSTEM_INT_SFTP 2 +#endif +#ifndef SUBSYSTEM_INT_SFTP_ERROR +#define SUBSYSTEM_INT_SFTP_ERROR 3 +#endif + +/* import */ +extern ServerOptions options; +extern struct sshauthopt *auth_opts; +char ** +do_setup_env_proxy(struct ssh *, Session *, const char *); + +/* +* do_exec* on Windows +* - Read and set user environment variables from registry +* - Build subsystem cmdline path +* - Interactive shell/commands are executed using ssh-shellhost.exe +* - ssh-shellhost.exe implements server-side PTY for Windows +*/ +#define UTF8_TO_UTF16_WITH_CLEANUP(o, i) do { \ + if (o != NULL) free(o); \ + if ((o = utf8_to_utf16(i)) == NULL) \ + goto cleanup; \ +} while (0) + +#define GOTO_CLEANUP_ON_ERR(exp) do { \ + if ((exp) != 0) \ + goto cleanup; \ +} while(0) + +/* TODO - built env var set and pass it along with CreateProcess */ +/* set user environment variables from user profile */ +static void +setup_session_user_vars(wchar_t* profile_path) +{ + /* retrieve and set env variables. */ + HKEY reg_key = 0; + wchar_t name[256]; + wchar_t path[PATH_MAX + 1] = { 0, }; + wchar_t *data = NULL, *data_expanded = NULL, *path_value = NULL, *to_apply; + DWORD type, name_chars = 256, data_chars = 0, data_expanded_chars = 0, required, i = 0; + LONG ret; + + SetEnvironmentVariableW(L"USERPROFILE", profile_path); + + if (profile_path[0] && profile_path[1] == L':') { + SetEnvironmentVariableW(L"HOMEPATH", profile_path + 2); + wchar_t wc = profile_path[2]; + profile_path[2] = L'\0'; + SetEnvironmentVariableW(L"HOMEDRIVE", profile_path); + profile_path[2] = wc; + } + else + SetEnvironmentVariableW(L"HOMEPATH", profile_path); + + swprintf_s(path, _countof(path), L"%s\\AppData\\Local", profile_path); + SetEnvironmentVariableW(L"LOCALAPPDATA", path); + swprintf_s(path, _countof(path), L"%s\\AppData\\Roaming", profile_path); + SetEnvironmentVariableW(L"APPDATA", path); + + ret = RegOpenKeyExW(HKEY_CURRENT_USER, L"Environment", 0, KEY_QUERY_VALUE, ®_key); + if (ret != ERROR_SUCCESS) + //error("Error retrieving user environment variables. RegOpenKeyExW returned %d", ret); + return; + else while (1) { + to_apply = NULL; + required = data_chars * sizeof(wchar_t); + name_chars = 256; + ret = RegEnumValueW(reg_key, i++, name, &name_chars, 0, &type, (LPBYTE)data, &required); + if (ret == ERROR_NO_MORE_ITEMS) + break; + else if (ret == ERROR_MORE_DATA || required > data_chars * 2) { + if (data != NULL) + free(data); + data = xmalloc(required); + data_chars = required / 2; + i--; + continue; + } + else if (ret != ERROR_SUCCESS) + break; + + if (type == REG_SZ) + to_apply = data; + else if (type == REG_EXPAND_SZ) { + required = ExpandEnvironmentStringsW(data, data_expanded, data_expanded_chars); + if (required > data_expanded_chars) { + if (data_expanded) + free(data_expanded); + data_expanded = xmalloc(required * 2); + data_expanded_chars = required; + ExpandEnvironmentStringsW(data, data_expanded, data_expanded_chars); + } + to_apply = data_expanded; + } + + if (_wcsicmp(name, L"PATH") == 0) { + if ((required = GetEnvironmentVariableW(L"PATH", NULL, 0)) != 0) { + /* "required" includes null term */ + path_value = xmalloc((wcslen(to_apply) + 1 + required) * 2); + GetEnvironmentVariableW(L"PATH", path_value, required); + path_value[required - 1] = L';'; + GOTO_CLEANUP_ON_ERR(memcpy_s(path_value + required, (wcslen(to_apply) + 1) * 2, to_apply, (wcslen(to_apply) + 1) * 2)); + to_apply = path_value; + } + + } + if (to_apply) + SetEnvironmentVariableW(name, to_apply); + } +cleanup: + if (reg_key) + RegCloseKey(reg_key); + if (data) + free(data); + if (data_expanded) + free(data_expanded); + if (path_value) + free(path_value); +} + +static int +setup_session_env(struct ssh *ssh, Session* s) +{ + int i = 0, ret = -1; + char *env_name = NULL, *env_value = NULL, *t = NULL, **env = NULL, *path_env_val = NULL; + char buf[1024] = { 0 }; + wchar_t *env_name_w = NULL, *env_value_w = NULL, *pw_dir_w = NULL, *tmp = NULL, wbuf[1024] = { 0, }; + char *laddr, *c; + + UTF8_TO_UTF16_WITH_CLEANUP(pw_dir_w, s->pw->pw_dir); + /* skip domain part (if present) while setting USERNAME */ + c = strchr(s->pw->pw_name, '\\'); + UTF8_TO_UTF16_WITH_CLEANUP(tmp, c ? c + 1 : s->pw->pw_name); + SetEnvironmentVariableW(L"USERNAME", tmp); + + if (!s->is_subsystem) { + _snprintf(buf, ARRAYSIZE(buf), "%s@%s", s->pw->pw_name, getenv("COMPUTERNAME")); + UTF8_TO_UTF16_WITH_CLEANUP(tmp, buf); + /* escape $ characters as $$ to distinguish from special prompt characters */ + for (int i = 0, j = 0; i < wcslen(tmp) && j < ARRAYSIZE(wbuf) - 1; i++) { + wbuf[j] = tmp[i]; + if (wbuf[j++] == L'$') + wbuf[j++] = L'$'; + } + wcscat_s(wbuf, ARRAYSIZE(wbuf), L" $P$G"); + SetEnvironmentVariableW(L"PROMPT", wbuf); + } + + setup_session_user_vars(pw_dir_w); /* setup user specific env variables */ + + env = do_setup_env_proxy(ssh, s, s->pw->pw_shell); + while (env_name = env[i]) { + if (t = strstr(env[i++], "=")) { + /* SKIP, if not applicable on WINDOWS + PATH is already set. + MAIL is not applicable. + */ + if ((0 == strncmp(env_name, "PATH=", strlen("PATH="))) || + (0 == strncmp(env_name, "MAIL=", strlen("MAIL=")))) { + continue; + } + + env_value = t + 1; + *t = '\0'; + UTF8_TO_UTF16_WITH_CLEANUP(env_name_w, env_name); + UTF8_TO_UTF16_WITH_CLEANUP(env_value_w, env_value); + SetEnvironmentVariableW(env_name_w, env_value_w); + } + } + ret = 0; + +cleanup: + if (pw_dir_w) + free(pw_dir_w); + if (tmp) + free(tmp); + if (env_name_w) + free(env_name_w); + if (env_value_w) + free(env_value_w); + if (env) { + i = 0; + while (t = env[i++]) + free(t); + free(env); + } + return ret; +} + +int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) { + int pipein[2], pipeout[2], pipeerr[2], r, ret = -1; + char *exec_command = NULL, *posix_cmd_input = NULL, *shell = NULL; + HANDLE job = NULL, process_handle; + extern char* shell_command_option; + extern BOOLEAN arg_escape; + + /* Create three pipes for stdin, stdout and stderr */ + if (pipe(pipein) == -1 || pipe(pipeout) == -1 || pipe(pipeerr) == -1) + goto cleanup; + + set_nonblock(pipein[0]); + set_nonblock(pipein[1]); + set_nonblock(pipeout[0]); + set_nonblock(pipeout[1]); + set_nonblock(pipeerr[0]); + set_nonblock(pipeerr[1]); + + fcntl(pipein[1], F_SETFD, FD_CLOEXEC); + fcntl(pipeout[0], F_SETFD, FD_CLOEXEC); + fcntl(pipeerr[0], F_SETFD, FD_CLOEXEC); + + /* setup Environment varibles */ + do { + static int environment_set = 0; + + if (environment_set) + break; + + if (setup_session_env(ssh, s) != 0) + goto cleanup; + + environment_set = 1; + } while (0); + int in_chroot = get_in_chroot(); + if (!in_chroot) + chdir(s->pw->pw_dir); + + if (s->is_subsystem >= SUBSYSTEM_INT_SFTP_ERROR) { + command = "echo This service allows sftp connections only."; + pty = 0; + } + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info; + HANDLE job_dup; + pid_t pid = -1; + char * shell_option = NULL; + int shell_len = 0; + /*account for the quotes and null*/ + shell_len = strlen(s->pw->pw_shell) + 2 + 1; + if ((shell = malloc(shell_len)) == NULL) { + errno = ENOMEM; + goto cleanup; + } + sprintf_s(shell, shell_len, "\"%s\"", s->pw->pw_shell); + debug3("shell: %s", shell); + + enum sh_type { SH_OTHER, SH_CMD, SH_PS, SH_BASH, SH_CYGWIN, SH_SHELLHOST } shell_type = SH_OTHER; + /* get shell type */ + if (strstr(s->pw->pw_shell, "system32\\cmd")) + shell_type = SH_CMD; + else if (strstr(s->pw->pw_shell, "powershell")) + shell_type = SH_PS; + else if (strstr(s->pw->pw_shell, "ssh-shellhost")) + shell_type = SH_SHELLHOST; + else if (strstr(s->pw->pw_shell, "\\bash")) + shell_type = SH_BASH; + else if (strstr(s->pw->pw_shell, "cygwin")) + shell_type = SH_CYGWIN; + + if (shell_command_option) + shell_option = shell_command_option; + else if (shell_type == SH_CMD) + shell_option = "/c"; + else + shell_option = "-c"; + debug3("shell_option: %s", shell_option); + + if (pty) { + fcntl(s->ptyfd, F_SETFD, FD_CLOEXEC); + if (exec_command_with_pty(&pid, shell, pipein[0], pipeout[1], pipeerr[1], s->col, s->row, s->ttyfd) == -1) + goto cleanup; + close(s->ttyfd); + s->ttyfd = -1; + } + else { + posix_spawn_file_actions_t actions; + char *spawn_argv[4] = { NULL, }; + exec_command = build_exec_command(command); + debug3("exec_command: %s", exec_command); + if (exec_command == NULL) + goto cleanup; + if (shell_type == SH_PS || shell_type == SH_BASH || + shell_type == SH_CYGWIN || (shell_type == SH_OTHER) && arg_escape) { + spawn_argv[0] = shell; + spawn_argv[1] = shell_option; + spawn_argv[2] = exec_command; + } + else { + /* + * no escaping needed for cmd and ssh-shellhost, or escaping is disabled + * in registry; pass shell, shell option, and quoted command as cmd path + * of posix_spawn to avoid escaping + */ + int posix_cmd_input_len = strlen(shell) + 1; + posix_cmd_input_len += strlen(shell_option) + 1; + /* account for " around and null */ + posix_cmd_input_len += strlen(exec_command) + 2 + 1; + + if ((posix_cmd_input = malloc(posix_cmd_input_len)) == NULL) { + errno = ENOMEM; + goto cleanup; + } + sprintf_s(posix_cmd_input, posix_cmd_input_len, "%s %s \"%s\"", + shell, shell_option, exec_command); + spawn_argv[0] = posix_cmd_input; + } + debug3("arg escape option: %s", arg_escape ? "TRUE":"FALSE"); + debug3("spawn_argv[0]: %s", spawn_argv[0]); + + if (posix_spawn_file_actions_init(&actions) != 0 || + posix_spawn_file_actions_adddup2(&actions, pipein[0], STDIN_FILENO) != 0 || + posix_spawn_file_actions_adddup2(&actions, pipeout[1], STDOUT_FILENO) != 0 || + posix_spawn_file_actions_adddup2(&actions, pipeerr[1], STDERR_FILENO) != 0) { + errno = EOTHER; + error("posix_spawn initialization failed"); + goto cleanup; + } + if (posix_spawn(&pid, spawn_argv[0], &actions, NULL, spawn_argv, NULL) != 0) { + errno = EOTHER; + error("posix_spawn: %s", strerror(errno)); + goto cleanup; + } + posix_spawn_file_actions_destroy(&actions); + } + + memset(&job_info, 0, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); + job_info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_BREAKAWAY_OK; + + if ((process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid)) == NULL) { + errno = EOTHER; + error("cannot get process handle: %d", GetLastError()); + goto cleanup; + } + + /* + * assign job object to control processes spawned + * 1. create job object + * 2. assign child to job object + * 3. duplicate job handle into child so it would be the last to close it + */ + if ((job = CreateJobObjectW(NULL, NULL)) == NULL || + !SetInformationJobObject(job, JobObjectExtendedLimitInformation, &job_info, sizeof(job_info)) || + !AssignProcessToJobObject(job, process_handle) || + !DuplicateHandle(GetCurrentProcess(), job, process_handle, &job_dup, 0, FALSE, DUPLICATE_SAME_ACCESS)) { + errno = EOTHER; + error("cannot associate job object: %d", GetLastError()); + TerminateProcess(process_handle, 255); + CloseHandle(process_handle); + goto cleanup; + } + s->pid = pid; + + /* Close the child sides of the socket pairs. */ + close(pipein[0]); + close(pipeout[1]); + close(pipeerr[1]); + + /* + * Enter the interactive session. Note: server_loop must be able to + * handle the case that fdin and fdout are the same. + */ + if (pty) { + /* Set interactive/non-interactive mode */ + packet_set_interactive(1, options.ip_qos_interactive, + options.ip_qos_bulk); + session_set_fds(ssh, s, pipein[1], pipeout[0], -1, 1, 1); + } + else { + /* Set interactive/non-interactive mode */ + packet_set_interactive(s->display != NULL, options.ip_qos_interactive, + options.ip_qos_bulk); + session_set_fds(ssh, s, pipein[1], pipeout[0], pipeerr[0], s->is_subsystem, 0); + } + + ret = 0; + +cleanup: + if (exec_command) + free(exec_command); + if (posix_cmd_input) + free(posix_cmd_input); + if (shell) + free(shell); + if (job) + CloseHandle(job); + + return ret; +} + +int +do_exec_no_pty(struct ssh *ssh, Session *s, const char *command) { + return do_exec_windows(ssh, s, command, 0); +} + +int +do_exec_pty(struct ssh *ssh, Session *s, const char *command) { + return do_exec_windows(ssh, s, command, 1); +} \ No newline at end of file diff --git a/contrib/win32/win32compat/w32fd.c b/contrib/win32/win32compat/w32fd.c index 6ce773b42..644cff2f7 100644 --- a/contrib/win32/win32compat/w32fd.c +++ b/contrib/win32/win32compat/w32fd.c @@ -1034,115 +1034,28 @@ int fork() verbose("fork is not supported"); return -1; } +char * build_commandline_string(const char* cmd, char *const argv[], BOOLEAN prepend_module_path); /* * spawn a child process * - specified by cmd with agruments argv * - with std handles set to in, out, err * - flags are passed to CreateProcess call -* -* cmd will be internally decoarated with a set of '"' -* to account for any spaces within the commandline -* this decoration is done only when additional arguments are passed in argv -* * spawned child will run as as_user if its not NULL */ - static int spawn_child_internal(char* cmd, char *const argv[], HANDLE in, HANDLE out, HANDLE err, unsigned long flags, HANDLE* as_user, BOOLEAN prepend_module_path) { PROCESS_INFORMATION pi; STARTUPINFOW si; BOOL b; - char *cmdline, *t; - char * const *t1; - DWORD cmdline_len = 0; + char *cmdline; wchar_t * cmdline_utf16 = NULL; - int add_module_path = 0, ret = -1; - char *path = NULL; - - if (!cmd) { - error("%s invalid argument cmd:%s", __func__, cmd); - return ret; - } - - if (!(path = _strdup(cmd))) { - error("failed to duplicate %s", cmd); - return ret; - } - - if (is_bash_test_env()) { - size_t len = strlen(path) + 1; - memset(path, 0, len); - - bash_to_win_path(cmd, path, len); - } - - 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) - cmdline_len += (DWORD)strlen(*t1++) + 1 + 2; - } - - if ((cmdline = malloc(cmdline_len)) == NULL) { + int ret = -1; + if ((cmdline = build_commandline_string(cmd, argv, prepend_module_path)) == NULL) { errno = ENOMEM; goto cleanup; } - - /* add current module path to start if needed */ - t = cmdline; - *t++ = '\"'; - if (add_module_path) { - memcpy(t, __progdir, strlen(__progdir)); - t += strlen(__progdir); - *t++ = '\\'; - } - - /* Add double quotes around the executable path - * path can be c:\cygwin64\bin\sh.exe "" - * Please note that, this logic is not just bash test specific. - */ - const char *exe_extenstion = ".exe"; - const char *tmp = NULL; - if ((tmp = strstr(path, exe_extenstion)) && (strlen(tmp) > strlen(exe_extenstion))) { - tmp += strlen(exe_extenstion); /* move the pointer to the end of ".exe" */ - - memcpy(t, path, strlen(path)-strlen(tmp)); - t += strlen(path) - strlen(tmp); - - *t++ = '\"'; - memcpy(t, tmp, strlen(tmp)); - t += strlen(tmp); - } else { - memcpy(t, path, strlen(path)); - t += strlen(path); - - *t++ = '\"'; - } - - if (argv) { - t1 = argv; - while (*t1) { - *t++ = ' '; - *t++ = '\"'; - memcpy(t, *t1, strlen(*t1)); - t += strlen(*t1); - *t++ = '\"'; - t1++; - } - } - - *t = '\0'; - if ((cmdline_utf16 = utf8_to_utf16(cmdline)) == NULL) { errno = ENOMEM; goto cleanup; @@ -1154,13 +1067,19 @@ spawn_child_internal(char* cmd, char *const argv[], HANDLE in, HANDLE out, HANDL si.hStdOutput = out; si.hStdError = err; si.dwFlags = STARTF_USESTDHANDLES; - - debug3("spawning %ls", cmdline_utf16); - if (as_user) - b = CreateProcessAsUserW(as_user, NULL, cmdline_utf16, NULL, NULL, TRUE, flags, NULL, NULL, &si, &pi); - else - b = CreateProcessW(NULL, cmdline_utf16, NULL, NULL, TRUE, flags, NULL, NULL, &si, &pi); + wchar_t * t = cmdline_utf16; + do { + debug3("spawning %ls", t); + if (as_user) + b = CreateProcessAsUserW(as_user, NULL, t, NULL, NULL, TRUE, flags, NULL, NULL, &si, &pi); + else + b = CreateProcessW(NULL, t, NULL, NULL, TRUE, flags, NULL, NULL, &si, &pi); + if(b || GetLastError() != ERROR_FILE_NOT_FOUND || (argv != NULL && *argv != NULL) || cmd[0] == '\"') + break; + t++; + *(cmdline_utf16 + wcslen(cmdline_utf16) - 1) = L'\0'; + } while (t == (cmdline_utf16 + 1)); if (b) { if (register_child(pi.hProcess, pi.dwProcessId) == -1) { @@ -1169,21 +1088,18 @@ spawn_child_internal(char* cmd, char *const argv[], HANDLE in, HANDLE out, HANDL goto cleanup; } CloseHandle(pi.hThread); + ret = pi.dwProcessId; } else { errno = GetLastError(); - error("%s failed error:%d", (as_user?"CreateProcessAsUserW":"CreateProcessW"), GetLastError()); - goto cleanup; + error("%s failed error:%d", (as_user ? "CreateProcessAsUserW" : "CreateProcessW"), GetLastError()); } - ret = pi.dwProcessId; cleanup: if (cmdline) free(cmdline); if (cmdline_utf16) free(cmdline_utf16); - if (path) - free(path); return ret; } @@ -1332,7 +1248,7 @@ posix_spawn_internal(pid_t *pidp, const char *path, const posix_spawn_file_actio for (i = 0; i < file_actions->num_aux_fds; i++) { aux_handles[i] = dup_handle(file_actions->aux_fds_info.parent_fd[i]); if (aux_handles[i] == NULL) - goto cleanup; + goto cleanup; } /* set fd info */ diff --git a/contrib/win32/win32compat/win32_pty.c b/contrib/win32/win32compat/win32_pty.c index 7c3636798..32661b1f6 100644 --- a/contrib/win32/win32compat/win32_pty.c +++ b/contrib/win32/win32compat/win32_pty.c @@ -31,7 +31,9 @@ #include #include "Debug.h" #include "inc\fcntl.h" +#include "inc\utf.h" #include "misc_internal.h" +#include "signal_internal.h" // Return Value: 0 for success, -1 for failure int @@ -96,33 +98,60 @@ int is_conpty_supported() return 0; } -int exec_command_with_pty(wchar_t* cmd, STARTUPINFOW* si, PROCESS_INFORMATION* pi, int ttyfd) +int exec_command_with_pty(int * pid, char* cmd, int in, int out, int err, unsigned int col, unsigned int row, int ttyfd) { - HANDLE ttyh = (HANDLE)w32_fd_to_handle(ttyfd); + PROCESS_INFORMATION pi; + STARTUPINFOW si; wchar_t pty_cmdline[MAX_CMD_LEN] = { 0, }; int ret = -1; + HANDLE ttyh = (HANDLE)w32_fd_to_handle(ttyfd); + wchar_t * cmd_w = NULL; + + if ((cmd_w = utf8_to_utf16(cmd)) == NULL) + return ret; + + memset(&si, 0, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + si.dwXCountChars = col; + si.dwYCountChars = row; + si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESIZE | STARTF_USECOUNTCHARS; + + si.hStdInput = (HANDLE)w32_fd_to_handle(in); + si.hStdOutput = (HANDLE)w32_fd_to_handle(out); + si.hStdError = (HANDLE)w32_fd_to_handle(err); + si.lpDesktop = NULL; if (is_conpty_supported()) - return CreateConPty(cmd, (short)si->dwXCountChars, (short)si->dwYCountChars, si->hStdInput, si->hStdOutput, ttyh, pi); + return CreateConPty(cmd_w, (short)si.dwXCountChars, (short)si.dwYCountChars, si.hStdInput, si.hStdOutput, ttyh, &pi); /* launch via "ssh-shellhost" -p command*/ - _snwprintf_s(pty_cmdline, MAX_CMD_LEN, MAX_CMD_LEN, L"\"%ls\\ssh-shellhost.exe\" ---pty %ls", __wprogdir, cmd); + _snwprintf_s(pty_cmdline, MAX_CMD_LEN, MAX_CMD_LEN, L"\"%ls\\ssh-shellhost.exe\" ---pty %ls", __wprogdir, cmd_w); /* * In PTY mode, ssh-shellhost takes stderr as control channel * TODO - fix this and pass control channel pipe as a command line parameter */ - si->hStdError = ttyh; + si.hStdError = ttyh; debug3("pty commandline: %ls", pty_cmdline); - if (!CreateProcessW(NULL, pty_cmdline, NULL, NULL, TRUE, 0, NULL, NULL, si, pi)) { + if (CreateProcessW(NULL, pty_cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { + if (register_child(pi.hProcess, pi.dwProcessId) == -1) { + TerminateProcess(pi.hProcess, 0); + CloseHandle(pi.hProcess); + goto done; + } + CloseHandle(pi.hThread); + } + else { debug("%s - failed to execute %ls, error:%d", __func__, pty_cmdline, GetLastError()); errno = EOTHER; goto done; } - + *pid = pi.dwProcessId; ret = 0; done: + if (cmd_w) + free(cmd_w); return ret; } \ No newline at end of file diff --git a/contrib/win32/win32compat/wmain_sshd.c b/contrib/win32/win32compat/wmain_sshd.c index 73364132b..e81e5db42 100644 --- a/contrib/win32/win32compat/wmain_sshd.c +++ b/contrib/win32/win32compat/wmain_sshd.c @@ -224,7 +224,9 @@ int argc_original = 0; wchar_t **wargv_original = NULL; int wmain(int argc, wchar_t **wargv) { - wchar_t* path_utf16; + wchar_t *path_value = NULL, *path_new_value; + errno_t result = 0; + size_t path_new_len = 0, len; argc_original = argc; wargv_original = wargv; @@ -232,6 +234,38 @@ int wmain(int argc, wchar_t **wargv) { /* change current directory to sshd.exe root */ _wchdir(__wprogdir); + /* + * we want to launch scp and sftp executables from the binary directory + * that sshd is hosted in. This will facilitate hosting and evaluating + * multiple versions of OpenSSH at the same time. + * it does not work well for powershell, cygwin, etc if program path is + * prepended to executable directory. + * To achive above, PATH is set to process environment + */ + _wdupenv_s(&path_value, &len, L"PATH"); + if (!path_value || (wcsstr(path_value, __wprogdir)) == NULL) { + path_new_len = wcslen(__wprogdir) + wcslen(path_value) + 2; + if ((path_new_value = (wchar_t *) malloc(path_new_len * sizeof(wchar_t))) == NULL) { + errno = ENOMEM; + error("failed to allocation memory"); + return -1; + } + swprintf_s(path_new_value, path_new_len, L"%s%s%s", __wprogdir, path_value ? L";" : L"", path_value); + if (result = _wputenv_s(L"PATH", path_new_value)) { + error("failed to set PATH environment variable: to value:%s, error:%d", path_new_value, result); + errno = result; + if (path_new_value) + free(path_new_value); + if(path_value) + free(path_value); + return -1; + } + if (path_new_value) + free(path_new_value); + if(path_value) + free(path_value); + } + if (!StartServiceCtrlDispatcherW(dispatch_table)) { if (GetLastError() == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) return sshd_main(argc, wargv); /* sshd running NOT as service*/ diff --git a/pal_doexec.h b/pal_doexec.h new file mode 100644 index 000000000..b4a798b8a --- /dev/null +++ b/pal_doexec.h @@ -0,0 +1,8 @@ +#ifndef _PAL_DOEXEC_H +#define _PAL_DOEXEC_H + +#include "session.h" + +int do_exec_pty(struct ssh *, Session *, const char *); +int do_exec_no_pty(struct ssh *, Session *, const char *); +#endif /* _PAL_DOEXEC_H */ \ No newline at end of file diff --git a/regress/pesterTests/SCP.Tests.ps1 b/regress/pesterTests/SCP.Tests.ps1 index c2973a39b..928f2a106 100644 --- a/regress/pesterTests/SCP.Tests.ps1 +++ b/regress/pesterTests/SCP.Tests.ps1 @@ -51,7 +51,7 @@ Describe "Tests for scp command" -Tags "CI" { Title = 'Simple copy local file to remote file' Source = $SourceFilePath Destination = "test_target:$DestinationFilePath" - Options = "-S '$sshcmd'" + Options = "-S `"$sshcmd`"" }, @{ Title = 'Simple copy remote file to local file' @@ -181,7 +181,7 @@ Describe "Tests for scp command" -Tags "CI" { $equal = @(Compare-Object (Get-ChildItem -path $SourceFilePath) (Get-ChildItem -path $DestinationFilePath) -Property Name, Length ).Length -eq 0 $equal | Should Be $true - if($Options.contains("-p")) + if($Options.contains("-p ")) { $equal = @(Compare-Object (Get-ChildItem -path $SourceFilePath).LastWriteTime.DateTime (Get-ChildItem -path $DestinationFilePath).LastWriteTime.DateTime ).Length -eq 0 $equal | Should Be $true @@ -198,7 +198,7 @@ Describe "Tests for scp command" -Tags "CI" { $equal = @(Compare-Object (Get-Item -path $SourceDir ) (Get-Item -path (join-path $DestinationDir $SourceDirName) ) -Property Name, Length).Length -eq 0 $equal | Should Be $true - if($Options.contains("-p")) + if($Options.contains("-p ")) { $equal = @(Compare-Object (Get-Item -path $SourceDir).LastWriteTime.DateTime (Get-Item -path (join-path $DestinationDir $SourceDirName)).LastWriteTime.DateTime).Length -eq 0 $equal | Should Be $true @@ -207,7 +207,7 @@ Describe "Tests for scp command" -Tags "CI" { $equal = @(Compare-Object (Get-ChildItem -Recurse -path $SourceDir) (Get-ChildItem -Recurse -path (join-path $DestinationDir $SourceDirName) ) -Property Name, Length).Length -eq 0 $equal | Should Be $true - if($Options.contains("-p") -and ($platform -eq [PlatformType]::Windows) -and ($PSVersionTable.PSVersion.Major -gt 2)) + if($Options.contains("-p ") -and ($platform -eq [PlatformType]::Windows) -and ($PSVersionTable.PSVersion.Major -gt 2)) { $equal = @(Compare-Object (Get-ChildItem -Recurse -path $SourceDir).LastWriteTime.DateTime (Get-ChildItem -Recurse -path (join-path $DestinationDir $SourceDirName) ).LastWriteTime.DateTime).Length -eq 0 $equal | Should Be $true diff --git a/regress/pesterTests/SSH.Tests.ps1 b/regress/pesterTests/SSH.Tests.ps1 index 5fc7d1b99..4eb113b01 100644 --- a/regress/pesterTests/SSH.Tests.ps1 +++ b/regress/pesterTests/SSH.Tests.ps1 @@ -76,6 +76,8 @@ Describe "E2E scenarios for ssh client" -Tags "CI" { $dfltShellRegPath = "HKLM:\Software\OpenSSH" $dfltShellRegKeyName = "DefaultShell" $dfltShellCmdOptionRegKeyName = "DefaultShellCommandOption" + Remove-ItemProperty -Path $dfltShellRegPath -Name $dfltShellRegKeyName -ErrorAction SilentlyContinue + Remove-ItemProperty -Path $dfltShellRegPath -Name $dfltShellCmdOptionRegKeyName -ErrorAction SilentlyContinue function ConfigureDefaultShell { param @@ -102,7 +104,7 @@ Describe "E2E scenarios for ssh client" -Tags "CI" { AfterEach {$tI++;} - Context "$tC - Basic Scenarios" { + Context "$tC - Basic Scenarios" { BeforeAll {$tI=1} AfterAll{$tC++} @@ -190,47 +192,107 @@ Describe "E2E scenarios for ssh client" -Tags "CI" { (dir $testdst2).Length | Should Be (dir $testsrc).Length } - } + } - Context "$tC - configure default shell Scenarios" { - BeforeAll {$tI=1} - AfterAll{$tC++} - AfterEach { - Remove-ItemProperty -Path $dfltShellRegPath -Name $dfltShellRegKeyName -ErrorAction SilentlyContinue - Remove-ItemProperty -Path $dfltShellRegPath -Name $dfltShellCmdOptionRegKeyName -ErrorAction SilentlyContinue - } - - It "$tC.$tI - default shell as powershell" -skip:$skip { + Context "$tC - configure powershell default shell Scenarios" { + BeforeAll { + $tI=1 $shell_path = (Get-Command powershell.exe -ErrorAction SilentlyContinue).path if($shell_path -ne $null) { - ConfigureDefaultShell -default_shell_path $shell_path -default_shell_cmd_option_val "/c" - - $o = ssh test_target Write-Output 1234 - $o | Should Be "1234" + ConfigureDefaultShell -default_shell_path $shell_path -default_shell_cmd_option_val "-c" } } + AfterAll{ + $tC++ + Remove-ItemProperty -Path $dfltShellRegPath -Name $dfltShellRegKeyName -ErrorAction SilentlyContinue + Remove-ItemProperty -Path $dfltShellRegPath -Name $dfltShellCmdOptionRegKeyName -ErrorAction SilentlyContinue + } - It "$tC.$tI - default shell as cmd" -skip:$skip { + It "$tC.$tI - basic powershell" -skip:$skip { + $o = ssh test_target Write-Output 1234 + $o | Should Be "1234" + } + + It "$tC.$tI - basic in powershell cmdlet" -skip:$skip { + $o = ssh test_target "cd `$env:ProgramFiles;pwd" + $LASTEXITCODE | Should Be 0 + #$o | Should Match "c:\Program Files" + } + It "$tC.$tI - powershell as default shell and double quotes in cmdline" { + # actual command line ssh target echo `"hello`" + $o = ssh test_target echo ``\`"hello``\`" + $o | Should Be "`"hello`"" + } + It "$tC.$tI - multiple commands with double quotes in powershell cmdlet" -skip:$skip { + # actual command line ssh target cd "$env:programfiles";pwd + $o = ssh test_target "cd \`"`$env:programfiles\`";pwd" + $LASTEXITCODE | Should Be 0 + $match = $o -match "Program Files" + $match.count | Should be 1 + } + It "$tC.$tI - multiple commands with double quotes in powershell cmdlet" -skip:$skip { + # actual command line ssh target dir "$env:programfiles";cd "$env:programfiles";pwd + $o = ssh test_target "dir \`"`$env:programfiles\`";cd \`"`$env:programfiles\`";pwd" + $LASTEXITCODE | Should Be 0 + #$o -contains "Program Files" | Should Be $True + $match = $o -match "Program Files" + $match.count | Should Be 3 + } + It "$tC.$tI - single quotes in powershell cmdlet" -skip:$skip { + # actual command line ssh target echo '$env:computername' + $o = ssh test_target "echo '`$env:computername'" + $LASTEXITCODE | Should Be 0 + $o | Should Be `$env:computername + } + } + Context "$tC - configure cmd as default shell" { + BeforeAll { + $tI=1 $shell_path = (Get-Command cmd.exe -ErrorAction SilentlyContinue).path if($shell_path -ne $null) { ConfigureDefaultShell -default_shell_path $shell_path -default_shell_cmd_option_val "/c" - - $o = ssh test_target where cmd - $o | Should Contain "cmd" - } } - - It "$tC.$tI - shellhost as default shell and multiple double quotes in cmdline" { - # actual command line ssh target \"cmd\" /c \"echo hello\" + } + AfterAll{ + $tC++ + Remove-ItemProperty -Path $dfltShellRegPath -Name $dfltShellRegKeyName -ErrorAction SilentlyContinue + Remove-ItemProperty -Path $dfltShellRegPath -Name $dfltShellCmdOptionRegKeyName -ErrorAction SilentlyContinue + } + It "$tC.$tI - default shell as cmd" -skip:$skip { + $o = ssh test_target where cmd + $o | Should Contain "cmd" + } + It "$tC.$tI - cmd as default shell and double quotes in cmdline" { + # actual command line ssh target echo "\"hello\"" + $o = ssh test_target 'echo "\"hello\""' + $o | Should Be "`"hello`"" + } + It "$tC.$tI - single quotes in powershell cmdlet" -skip:$skip { + # actual command line ssh target echo '$env:computername' + $o = ssh test_target "echo 'hello'" + $LASTEXITCODE | Should Be 0 + $o | Should Be "'hello'" + } + } + Context "$tC - configure ssh-shellhost as default shell" { + BeforeAll { + $tI=1 $shell_path = (Get-Command ssh-shellhost -ErrorAction SilentlyContinue).path ConfigureDefaultShell -default_shell_path $shell_path - $o = ssh test_target `\`"cmd`\`" /c `\`"echo hello`\`" - $o | Should Be "hello" + } + AfterAll{ + $tC++ + Remove-ItemProperty -Path $dfltShellRegPath -Name $dfltShellRegKeyName -ErrorAction SilentlyContinue + Remove-ItemProperty -Path $dfltShellRegPath -Name $dfltShellCmdOptionRegKeyName -ErrorAction SilentlyContinue + } + It "$tC.$tI - shellhost as default shell and multiple double quotes in cmdline" { + # actual command line ssh target \"cmd\" /c \"echo \"hello\"\" + $o = ssh test_target `\`"cmd`\`" /c `\`"echo \`"hello\`"`\`" + $o | Should Be "`"hello`"" } } - Context "$tC - cmdline parameters" { - + Context "$tC - cmdline parameters" { BeforeAll {$tI=1} AfterAll{$tC++} @@ -298,16 +360,17 @@ Describe "E2E scenarios for ssh client" -Tags "CI" { } It "ProxyCommand with file name only" { - & cmd /c "ssh -o ProxyCommand=`"cmd.exe /c echo Invalid proxy 1>&2`" abc 2>$stderrFile" - $stderrFile | Should Contain "Invalid proxy" + & cmd /c "ssh -o ProxyCommand=`"cmd.exe /c echo test string for invalid proxy 1>&2`" abc 2>$stderrFile" + $stderrFile | Should Contain "test string for invalid proxy" + write-host (Get-Content $stderrFile) $stderrFile | Should Contain "Connection closed by remote host" } It "ProxyCommand with absolute path to the file" { - & cmd /c "ssh -o ProxyCommand=`"$($env:ComSpec) /c echo Invalid proxy 1>&2`" abc 2>$stderrFile" - $stderrFile | Should Contain "Invalid proxy" + & cmd /c "ssh -o ProxyCommand=`"$($env:ComSpec) /c echo test string for invalid proxy 1>&2`" abc 2>$stderrFile" + $stderrFile | Should Contain "test string for invalid proxy" + write-host (Get-Content $stderrFile) $stderrFile | Should Contain "Connection closed by remote host" } - } - + } } diff --git a/regress/unittests/win32compat/miscellaneous_tests.c b/regress/unittests/win32compat/miscellaneous_tests.c index fbebca88d..00763b8d9 100644 --- a/regress/unittests/win32compat/miscellaneous_tests.c +++ b/regress/unittests/win32compat/miscellaneous_tests.c @@ -318,98 +318,99 @@ test_chroot() } void -test_build_session_commandline() +test_build_exec_command() { - char *progdir = __progdir, *out, buf[PATH_MAX * 2]; + char *out; - TEST_START("default interactive session tests"); - out = build_session_commandline("c:\\system32\\cmd.exe", NULL, NULL); - ASSERT_STRING_EQ(out, "\"c:\\system32\\cmd.exe\""); + TEST_START("arg is null"); + out = build_exec_command(NULL); + ASSERT_PTR_EQ(out, NULL); TEST_DONE(); - TEST_START("cmd shell tests"); - buf[0] = '\0'; - strcat(buf, "\"c:\\system32\\cmd.exe\" /c \"\""); - strcat(buf, progdir); - int len_pg = strlen(buf); - out = build_session_commandline("c:\\system32\\cmd.exe", NULL, "internal-sftp -arg"); - buf[len_pg] = '\0'; - strcat(buf, "\\sftp-server.exe\" -arg\""); - ASSERT_STRING_EQ(out, buf); - out = build_session_commandline("c:\\system32\\cmd.exe", NULL, "SFTP-server.exe -arg"); - buf[len_pg] = '\0'; - strcat(buf, "\\sftp-server.exe\" -arg\""); - ASSERT_STRING_EQ(out, buf); - out = build_session_commandline("c:\\system32\\cmd.exe", NULL, "sftp-SERVER -arg"); - buf[len_pg] = '\0'; - strcat(buf, "\\sftp-server.exe\" -arg\""); - ASSERT_STRING_EQ(out, buf); - out = build_session_commandline("c:\\system32\\cmd.exe", NULL, "sCp -arg"); - buf[len_pg] = '\0'; - strcat(buf, "\\scp.exe\" -arg\""); - ASSERT_STRING_EQ(out, buf); + TEST_START("scp tests"); + out = build_exec_command("sCp -arg"); + ASSERT_STRING_EQ(out, "scp.exe -arg"); free(out); - TEST_DONE(); - - TEST_START("bash shell tests"); - out = build_session_commandline("c:\\system32\\bash.exe", NULL, "internal-sftp -arg"); - ASSERT_STRING_EQ(out, "\"c:\\system32\\bash.exe\" -c \"sftp-server.exe -arg\""); - free(out); - out = build_session_commandline("c:\\system32\\bash", NULL, "internal-sftp -arg"); - ASSERT_STRING_EQ(out, "\"c:\\system32\\bash\" -c \"sftp-server.exe -arg\""); - free(out); - out = build_session_commandline("c:\\system32\\bash", NULL, "sFTP-server -arg"); - ASSERT_STRING_EQ(out, "\"c:\\system32\\bash\" -c \"sftp-server.exe -arg\""); - free(out); - out = build_session_commandline("c:\\system32\\bash", NULL, "scP -arg"); - ASSERT_STRING_EQ(out, "\"c:\\system32\\bash\" -c \"scp.exe -arg\""); - free(out); - out = build_session_commandline("c:\\cygwin\\bash.exe", NULL, "internal-sftp -arg"); - ASSERT_STRING_EQ(out, "\"c:\\cygwin\\bash.exe\" -c \"sftp-server.exe -arg\""); - free(out); - out = build_session_commandline("c:\\cygwin\\bash", NULL, "sftp-server -arg"); - ASSERT_STRING_EQ(out, "\"c:\\cygwin\\bash\" -c \"sftp-server.exe -arg\""); - free(out); - out = build_session_commandline("c:\\cygwin\\bash", NULL, "sftp-seRVer.exe -arg"); - ASSERT_STRING_EQ(out, "\"c:\\cygwin\\bash\" -c \"sftp-server.exe -arg\""); - free(out); - out = build_session_commandline("c:\\cygwin\\bash", NULL, "sCp -arg"); - ASSERT_STRING_EQ(out, "\"c:\\cygwin\\bash\" -c \"scp.exe -arg\""); + out = build_exec_command("sCp.exe -arg1 -arg2"); + ASSERT_STRING_EQ(out, "scp.exe -arg1 -arg2"); free(out); TEST_DONE(); - TEST_START("powershell shell tests"); - out = build_session_commandline("c:\\powershell.exe", NULL, "internal-sftp -arg"); - ASSERT_STRING_EQ(out, "\"c:\\powershell.exe\" -c \"sftp-server.exe -arg\""); + TEST_START("sftp tests"); + out = build_exec_command("internal-sftp \"arg1 arg2\""); + ASSERT_STRING_EQ(out, "sftp-server.exe \"arg1 arg2\""); free(out); - out = build_session_commandline("c:\\powershell", NULL, "sftp-server -arg"); - ASSERT_STRING_EQ(out, "\"c:\\powershell\" -c \"sftp-server.exe -arg\""); + out = build_exec_command("SFTP-server.exe -arg"); + ASSERT_STRING_EQ(out, "sftp-server.exe -arg"); free(out); - out = build_session_commandline("c:\\powershell.exe", NULL, "sftp-sERver.exe -arg"); - ASSERT_STRING_EQ(out, "\"c:\\powershell.exe\" -c \"sftp-server.exe -arg\""); - free(out); - out = build_session_commandline("c:\\powershell.exe", NULL, "scP -arg"); - ASSERT_STRING_EQ(out, "\"c:\\powershell.exe\" -c \"scp.exe -arg\""); - free(out); - TEST_DONE(); - - - TEST_START("other shell tests"); - out = build_session_commandline("c:\\myshell.exe", NULL, "internal-sftp -arg"); - ASSERT_STRING_EQ(out, "\"c:\\myshell.exe\" -c \"sftp-server.exe -arg\""); - free(out); - out = build_session_commandline("c:\\myshell", NULL, "sftp-server -arg"); - ASSERT_STRING_EQ(out, "\"c:\\myshell\" -c \"sftp-server.exe -arg\""); - free(out); - out = build_session_commandline("c:\\myshell", NULL, "sftp-seRVer.exe -arg"); - ASSERT_STRING_EQ(out, "\"c:\\myshell\" -c \"sftp-server.exe -arg\""); - free(out); - out = build_session_commandline("c:\\myshell", NULL, "sCp -arg"); - ASSERT_STRING_EQ(out, "\"c:\\myshell\" -c \"scp.exe -arg\""); + out = build_exec_command("sftp-SERVER -arg"); + ASSERT_STRING_EQ(out, "sftp-server.exe -arg"); free(out); TEST_DONE(); } +void +test_build_commandline_string() +{ + char *out, in[PATH_MAX], buf[PATH_MAX]; + + TEST_START("cmd is null"); + out = build_commandline_string(NULL, NULL, TRUE); + ASSERT_PTR_EQ(out, NULL); + TEST_DONE(); + + TEST_START("arg is null"); + out = build_commandline_string("\"c:\\windows\\system32\\cmd.exe\" /c arg", NULL, FALSE); + ASSERT_STRING_EQ(out, "\"c:\\windows\\system32\\cmd.exe\" /c arg"); + free(out); + out = build_commandline_string("cmd.exe /c ping.exe", NULL, FALSE); + ASSERT_STRING_EQ(out, "\"cmd.exe /c ping.exe\""); + sprintf_s(in, PATH_MAX, "\"%s\\%s\"", __progdir, "ssh-shellhost.exe\" -c \"arg1 arg2\""); + out = build_commandline_string(in, NULL, TRUE); + ASSERT_STRING_EQ(out, in); + out = build_commandline_string("\"ssh-shellhost.exe\" -c \"arg1 arg2\"", NULL, TRUE); + sprintf_s(buf, PATH_MAX, "\"%s\\%s", __progdir, "ssh-shellhost.exe\" -c \"arg1 arg2\""); + ASSERT_STRING_EQ(out, buf); + free(out); + out = build_commandline_string("\"cmd.exe\" /c \"arg1 arg2\"", NULL, FALSE); + ASSERT_STRING_EQ(out, "\"cmd.exe\" /c \"arg1 arg2\""); + free(out); + TEST_DONE(); + + char *argv[4] = { NULL, }; + argv[0] = "\"C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\""; + argv[1] = "-c"; + argv[2] = "arg1 arg2"; + TEST_START("arg is not null"); + out = build_commandline_string(argv[0], argv + 1, TRUE); + sprintf_s(buf, PATH_MAX, "%s %s %s", argv[0], argv[1], "\"arg1 arg2\""); + ASSERT_STRING_EQ(out, buf); + free(out); + argv[0] = "C:\\my folder\\bash.exe"; + argv[2] = "\"arg1\\arg2\""; + out = build_commandline_string(argv[0], argv + 1, TRUE); + sprintf_s(buf, PATH_MAX, "\"%s\" %s %s", argv[0], argv[1], "\\\"arg1\\arg2\\\""); + ASSERT_STRING_EQ(out, buf); + free(out); + argv[2] = "\"arg1 arg2\\\""; + out = build_commandline_string(argv[0], argv + 1, TRUE); + sprintf_s(buf, PATH_MAX, "\"%s\" %s %s", argv[0], argv[1], "\"\\\"arg1 arg2\\\\\\\"\""); + ASSERT_STRING_EQ(out, buf); + free(out); + argv[0] = "\"c:\\cygwin64\\bin\\ba.exe\""; + argv[2] = "arg1\\arg2"; + out = build_commandline_string(argv[0], argv + 1, TRUE); + sprintf_s(buf, PATH_MAX, "%s %s %s", argv[0], argv[1], "arg1\\arg2"); + ASSERT_STRING_EQ(out, buf); + free(out); + argv[0] = "\"c:\\cygwin64\\bin\\ba.exe\""; + argv[2] = "'arg1 \\arg2\\\"'"; + out = build_commandline_string(argv[0], argv + 1, TRUE); + sprintf_s(buf, PATH_MAX, "%s %s %s", argv[0], argv[1], "'arg1 \\arg2\\\\\\\"'"); + ASSERT_STRING_EQ(out, buf); + free(out); + TEST_DONE(); +} void miscellaneous_tests() @@ -421,5 +422,6 @@ miscellaneous_tests() test_realpath(); test_statvfs(); test_chroot(); - test_build_session_commandline(); + test_build_exec_command(); + test_build_commandline_string(); } diff --git a/session.c b/session.c index f9355c08d..2da4f0aa6 100644 --- a/session.c +++ b/session.c @@ -96,6 +96,7 @@ #include "monitor_wrap.h" #include "sftp.h" #include "atomicio.h" +#include "pal_doexec.h" #if defined(KRB5) && defined(USE_AFS) #include @@ -118,8 +119,10 @@ void session_set_fds(struct ssh *, Session *, int, int, int, int, int); void session_pty_cleanup(Session *); void session_proctitle(Session *); int session_setup_x11fwd(struct ssh *, Session *); +#ifndef WINDOWS /* !WINDOWS */ int do_exec_pty(struct ssh *, Session *, const char *); int do_exec_no_pty(struct ssh *, Session *, const char *); +#endif int do_exec(struct ssh *, Session *, const char *); void do_login(struct ssh *, Session *, const char *); void do_child(struct ssh *, Session *, const char *); @@ -385,351 +388,7 @@ xauth_valid_string(const char *s) #define USE_PIPES 1 -#ifdef WINDOWS -/* - * do_exec* on Windows - * - Read and set user environment variables from registry - * - Build subsystem cmdline path - * - Interactive shell/commands are executed using ssh-shellhost.exe - * - ssh-shellhost.exe implements server-side PTY for Windows - */ -static char ** do_setup_env(struct ssh *ssh, Session *s, const char *shell); - -#define UTF8_TO_UTF16_WITH_CLEANUP(o, i) do { \ - if (o != NULL) free(o); \ - if ((o = utf8_to_utf16(i)) == NULL) \ - goto cleanup; \ -} while (0) - -#define GOTO_CLEANUP_ON_ERR(exp) do { \ - if ((exp) != 0) \ - goto cleanup; \ -} while(0) - -/* TODO - built env var set and pass it along with CreateProcess */ -/* set user environment variables from user profile */ -static void -setup_session_user_vars(wchar_t* profile_path) -{ - /* retrieve and set env variables. */ - HKEY reg_key = 0; - wchar_t name[256]; - wchar_t path[PATH_MAX + 1] = { 0, }; - wchar_t *data = NULL, *data_expanded = NULL, *path_value = NULL, *to_apply; - DWORD type, name_chars = 256, data_chars = 0, data_expanded_chars = 0, required, i = 0; - LONG ret; - - SetEnvironmentVariableW(L"USERPROFILE", profile_path); - - if (profile_path[0] && profile_path[1] == L':') { - SetEnvironmentVariableW(L"HOMEPATH", profile_path + 2); - wchar_t wc = profile_path[2]; - profile_path[2] = L'\0'; - SetEnvironmentVariableW(L"HOMEDRIVE", profile_path); - profile_path[2] = wc; - } else { - SetEnvironmentVariableW(L"HOMEPATH", profile_path); - } - - swprintf_s(path, _countof(path), L"%s\\AppData\\Local", profile_path); - SetEnvironmentVariableW(L"LOCALAPPDATA", path); - swprintf_s(path, _countof(path), L"%s\\AppData\\Roaming", profile_path); - SetEnvironmentVariableW(L"APPDATA", path); - - ret = RegOpenKeyExW(HKEY_CURRENT_USER, L"Environment", 0, KEY_QUERY_VALUE, ®_key); - if (ret != ERROR_SUCCESS) - //error("Error retrieving user environment variables. RegOpenKeyExW returned %d", ret); - return; - else while (1) { - to_apply = NULL; - required = data_chars * sizeof(wchar_t); - name_chars = 256; - ret = RegEnumValueW(reg_key, i++, name, &name_chars, 0, &type, (LPBYTE)data, &required); - if (ret == ERROR_NO_MORE_ITEMS) - break; - else if (ret == ERROR_MORE_DATA || required > data_chars * 2) { - if (data != NULL) - free(data); - data = xmalloc(required); - data_chars = required / 2; - i--; - continue; - } - else if (ret != ERROR_SUCCESS) - break; - - if (type == REG_SZ) - to_apply = data; - else if (type == REG_EXPAND_SZ) { - required = ExpandEnvironmentStringsW(data, data_expanded, data_expanded_chars); - if (required > data_expanded_chars) { - if (data_expanded) - free(data_expanded); - data_expanded = xmalloc(required * 2); - data_expanded_chars = required; - ExpandEnvironmentStringsW(data, data_expanded, data_expanded_chars); - } - to_apply = data_expanded; - } - - if (_wcsicmp(name, L"PATH") == 0) { - if ((required = GetEnvironmentVariableW(L"PATH", NULL, 0)) != 0) { - /* "required" includes null term */ - path_value = xmalloc((wcslen(to_apply) + 1 + required) * 2); - GetEnvironmentVariableW(L"PATH", path_value, required); - path_value[required - 1] = L';'; - GOTO_CLEANUP_ON_ERR(memcpy_s(path_value + required, (wcslen(to_apply) + 1) * 2, to_apply, (wcslen(to_apply) + 1) * 2)); - to_apply = path_value; - } - - } - if (to_apply) - SetEnvironmentVariableW(name, to_apply); - } -cleanup: - if (reg_key) - RegCloseKey(reg_key); - if (data) - free(data); - if (data_expanded) - free(data_expanded); - if (path_value) - free(path_value); -} - -static int -setup_session_env(struct ssh *ssh, Session* s) -{ - int i = 0, ret = -1; - char *env_name = NULL, *env_value = NULL, *t = NULL, **env = NULL, *path_env_val = NULL; - char buf[1024] = { 0 }; - wchar_t *env_name_w = NULL, *env_value_w = NULL, *pw_dir_w = NULL, *tmp = NULL, wbuf[1024] = { 0, }; - char *laddr, *c; - - UTF8_TO_UTF16_WITH_CLEANUP(pw_dir_w, s->pw->pw_dir); - /* skip domain part (if present) while setting USERNAME */ - c = strchr(s->pw->pw_name, '\\'); - UTF8_TO_UTF16_WITH_CLEANUP(tmp, c ? c + 1 : s->pw->pw_name); - SetEnvironmentVariableW(L"USERNAME", tmp); - - if (!s->is_subsystem) { - _snprintf(buf, ARRAYSIZE(buf), "%s@%s", s->pw->pw_name, getenv("COMPUTERNAME")); - UTF8_TO_UTF16_WITH_CLEANUP(tmp, buf); - /* escape $ characters as $$ to distinguish from special prompt characters */ - for (int i = 0, j = 0; i < wcslen(tmp) && j < ARRAYSIZE(wbuf) - 1; i++) { - wbuf[j] = tmp[i]; - if (wbuf[j++] == L'$') - wbuf[j++] = L'$'; - } - wcscat_s(wbuf, ARRAYSIZE(wbuf), L" $P$G"); - SetEnvironmentVariableW(L"PROMPT", wbuf); - } - - setup_session_user_vars(pw_dir_w); /* setup user specific env variables */ - - env = do_setup_env(ssh, s, s->pw->pw_shell); - while (env_name = env[i]) { - if (t = strstr(env[i++], "=")) { - /* SKIP, if not applicable on WINDOWS - PATH is already set. - MAIL is not applicable. - */ - if ((0 == strncmp(env_name, "PATH=", strlen("PATH="))) || - (0 == strncmp(env_name, "MAIL=", strlen("MAIL=")))) { - continue; - } - - env_value = t + 1; - *t = '\0'; - UTF8_TO_UTF16_WITH_CLEANUP(env_name_w, env_name); - UTF8_TO_UTF16_WITH_CLEANUP(env_value_w, env_value); - - SetEnvironmentVariableW(env_name_w, env_value_w); - } - } - - ret = 0; -cleanup : - if (pw_dir_w) - free(pw_dir_w); - - if (tmp) - free(tmp); - - if (env_name_w) - free(env_name_w); - - if (env_value_w) - free(env_value_w); - - if (env) { - i = 0; - while (t = env[i++]) - free(t); - - free(env); - } - - return ret; -} - -int register_child(void* child, unsigned long pid); -char* build_session_commandline(const char *, const char *, const char *); - -int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) { - int pipein[2], pipeout[2], pipeerr[2], r, ret = -1; - wchar_t *exec_command_w = NULL; - char *exec_command = NULL; - HANDLE job = NULL; - extern char* shell_command_option; - - /* Create three pipes for stdin, stdout and stderr */ - if (pipe(pipein) == -1 || pipe(pipeout) == -1 || pipe(pipeerr) == -1) - goto cleanup; - - set_nonblock(pipein[0]); - set_nonblock(pipein[1]); - set_nonblock(pipeout[0]); - set_nonblock(pipeout[1]); - set_nonblock(pipeerr[0]); - set_nonblock(pipeerr[1]); - - fcntl(pipein[1], F_SETFD, FD_CLOEXEC); - fcntl(pipeout[0], F_SETFD, FD_CLOEXEC); - fcntl(pipeerr[0], F_SETFD, FD_CLOEXEC); - - /* setup Environment varibles */ - do { - static int environment_set = 0; - - if (environment_set) - break; - - if (setup_session_env(ssh, s) != 0) - goto cleanup; - - environment_set = 1; - } while (0); - - if (!in_chroot) - chdir(s->pw->pw_dir); - - if (s->is_subsystem >= SUBSYSTEM_INT_SFTP_ERROR) { - command = "echo This service allows sftp connections only."; - pty = 0; - } - - exec_command = build_session_commandline(s->pw->pw_shell, shell_command_option, command); - if (exec_command == NULL) - goto cleanup; - - /* start the process */ - { - PROCESS_INFORMATION pi; - STARTUPINFOW si; - JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info; - HANDLE job_dup; - - memset(&si, 0, sizeof(STARTUPINFO)); - si.cb = sizeof(STARTUPINFO); - si.dwXSize = 5; - si.dwYSize = 5; - si.dwXCountChars = s->col; - si.dwYCountChars = s->row; - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESIZE | STARTF_USECOUNTCHARS; - - si.hStdInput = (HANDLE)w32_fd_to_handle(pipein[0]); - si.hStdOutput = (HANDLE)w32_fd_to_handle(pipeout[1]); - si.hStdError = (HANDLE)w32_fd_to_handle(pipeerr[1]); - si.lpDesktop = NULL; - - if ((exec_command_w = utf8_to_utf16(exec_command)) == NULL) - goto cleanup; - - debug("Executing command: %s with%spty", exec_command, pty? " ":" no "); - - if (pty) { - fcntl(s->ptyfd, F_SETFD, FD_CLOEXEC); - if (exec_command_with_pty(exec_command_w, &si, &pi, s->ttyfd) == -1) - goto cleanup; - close(s->ttyfd); - s->ttyfd = -1; - } else if (!CreateProcessW(NULL, exec_command_w, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { - errno = EOTHER; - error("ERROR. Cannot create process (%u).\n", GetLastError()); - goto cleanup; - } - - CloseHandle(pi.hThread); - memset(&job_info, 0, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); - job_info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_BREAKAWAY_OK; - - /* - * assign job object to control processes spawned by shell - * 1. create job object - * 2. assign child to job object - * 3. duplicate job handle into child so it would be the last to close it - */ - if ((job = CreateJobObjectW(NULL, NULL)) == NULL || - !SetInformationJobObject(job, JobObjectExtendedLimitInformation, &job_info, sizeof(job_info)) || - !AssignProcessToJobObject(job, pi.hProcess) || - !DuplicateHandle(GetCurrentProcess(), job, pi.hProcess, &job_dup, 0, FALSE, DUPLICATE_SAME_ACCESS)) { - error("cannot associate job object: %d", GetLastError()); - errno = EOTHER; - TerminateProcess(pi.hProcess, 255); - CloseHandle(pi.hProcess); - goto cleanup; - } - - s->pid = pi.dwProcessId; - register_child(pi.hProcess, pi.dwProcessId); - } - - /* Close the child sides of the socket pairs. */ - close(pipein[0]); - close(pipeout[1]); - close(pipeerr[1]); - - /* - * Enter the interactive session. Note: server_loop must be able to - * handle the case that fdin and fdout are the same. - */ - if (pty) { - /* Set interactive/non-interactive mode */ - packet_set_interactive(1, options.ip_qos_interactive, - options.ip_qos_bulk); - session_set_fds(ssh, s, pipein[1], pipeout[0], -1, 1, 1); - } else { - /* Set interactive/non-interactive mode */ - packet_set_interactive(s->display != NULL, options.ip_qos_interactive, - options.ip_qos_bulk); - session_set_fds(ssh, s, pipein[1], pipeout[0], pipeerr[0], s->is_subsystem, 0); - } - - ret = 0; - -cleanup: - if (!exec_command) - free(exec_command); - if (!exec_command_w) - free(exec_command_w); - if (job) - CloseHandle(job); - - return ret; -} - -int -do_exec_no_pty(struct ssh *ssh, Session *s, const char *command) { - return do_exec_windows(ssh, s, command, 0); -} - -int -do_exec_pty(struct ssh *ssh, Session *s, const char *command) { - return do_exec_windows(ssh, s, command, 1); -} - -#else /* !WINDOWS */ +#ifndef WINDOWS /* !WINDOWS */ /* * This is called to fork and execute a command when we have no tty. This * will call do_child from the child, and server_loop from the parent after @@ -3081,4 +2740,23 @@ session_get_remote_name_or_ip(struct ssh *ssh, u_int utmp_size, int use_dns) remote = ssh_remote_ipaddr(ssh); return remote; } +/* +* Since in_chroot is static for now, create this function +* to have unix code intact +*/ +#ifdef WINDOWS +int get_in_chroot() +{ + return in_chroot; +} +/* + * Since do_setup_env is static for now, create this function + * to have unix code intact +*/ +char ** +do_setup_env_proxy(struct ssh *ssh, Session *s, const char *shell) +{ + return do_setup_env(ssh, s, shell); +} +#endif \ No newline at end of file