diff --git a/contrib/win32/win32compat/misc.c b/contrib/win32/win32compat/misc.c index 025abcc3f..3441ad6d9 100644 --- a/contrib/win32/win32compat/misc.c +++ b/contrib/win32/win32compat/misc.c @@ -1720,3 +1720,178 @@ 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, int pty) +{ + enum sh_type { SH_CMD, SH_PS, SH_WSL_BASH, SH_CYGWIN, SH_OTHER } shell_type = SH_OTHER; + enum cmd_type { CMD_OTHER, CMD_SFTP, CMD_SCP } command_type = CMD_OTHER; + char *progdir = w32_programdir(), *cmd_sp = NULL, *cmdline = NULL, *ret = NULL, *p; + int len, progdir_len = (int)strlen(progdir); + +#define CMDLINE_APPEND(P, S) \ +do { \ + int _S_len = (int)strlen(S); \ + memcpy((P), (S), _S_len); \ + (P) += _S_len; \ +} while(0) + + /* 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, "system32\\bash")) + shell_type = SH_WSL_BASH; + else if (strstr(shell, "cygwin")) + shell_type = SH_CYGWIN; + + /* 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); + + 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) { + + 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"); + } + + CMDLINE_APPEND(p, command_args); + *p = '\0'; + command = cmd_sp; + } while (0); + + len = 0; + if (pty) + len += progdir_len + (int)strlen("ssh-shellhost.exe") + 5; + 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 */ + len += (int)strlen(command) + 5; /* 5 for possible " around command and null term*/ + } + + if ((cmdline = malloc(len)) == NULL) { + errno = ENOMEM; + goto done; + } + + p = cmdline; + if (pty) { + CMDLINE_APPEND(p, "\""); + CMDLINE_APPEND(p, progdir); + CMDLINE_APPEND(p, "\\ssh-shellhost.exe\" "); + } + 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 "); + else + CMDLINE_APPEND(p, " -c "); + + /* bash type shells require " decoration around command*/ + if (shell_type == SH_WSL_BASH || shell_type == SH_CYGWIN) + CMDLINE_APPEND(p, "\""); + + CMDLINE_APPEND(p, command); + + if (shell_type == SH_WSL_BASH || shell_type == SH_CYGWIN) + CMDLINE_APPEND(p, "\""); + } + *p = '\0'; + ret = cmdline; + cmdline = NULL; +done: + if (cmd_sp) + free(cmd_sp); + if (cmdline) + free(cmdline); + + return ret; +} \ No newline at end of file diff --git a/contrib/win32/win32compat/misc_internal.h b/contrib/win32/win32compat/misc_internal.h index 24fa9001a..1bc815312 100644 --- a/contrib/win32/win32compat/misc_internal.h +++ b/contrib/win32/win32compat/misc_internal.h @@ -54,3 +54,4 @@ int is_absolute_path(const char *); int file_in_chroot_jail(HANDLE, const char*); PSID get_user_sid(char*); int am_system(); +char* build_session_commandline(const char *, const char *, const char *, int ); diff --git a/contrib/win32/win32compat/pwd.c b/contrib/win32/win32compat/pwd.c index a1793a247..04a764d2d 100644 --- a/contrib/win32/win32compat/pwd.c +++ b/contrib/win32/win32compat/pwd.c @@ -94,6 +94,7 @@ set_defaultshell() if ((command_option_local = utf16_to_utf8(option_buf)) == NULL) goto cleanup; + convertToBackslash(pw_shellpath_local); pw_shellpath = pw_shellpath_local; pw_shellpath_local = NULL; shell_command_option = command_option_local; diff --git a/regress/unittests/win32compat/miscellaneous_tests.c b/regress/unittests/win32compat/miscellaneous_tests.c index 7f54023b2..bbddf24a9 100644 --- a/regress/unittests/win32compat/miscellaneous_tests.c +++ b/regress/unittests/win32compat/miscellaneous_tests.c @@ -311,6 +311,137 @@ test_chroot() //_wsystem(L"RD /S /Q chroot-testdir >NUL 2>&1"); } +void +test_build_session_commandline() +{ + char *progdir = w32_programdir(), *out, buf[PATH_MAX*2], shellhost_path[PATH_MAX]; + shellhost_path[0] = '\0'; + strcat(shellhost_path, "\""); + strcat(shellhost_path, progdir); + strcat(shellhost_path, "\\ssh-shellhost.exe\""); + int shellhost_path_len = (int)strlen(shellhost_path); + + TEST_START("default interactive session tests"); + out = build_session_commandline("c:\\system32\\cmd.exe", NULL, NULL, 0); + ASSERT_STRING_EQ(out, "\"c:\\system32\\cmd.exe\""); + out = build_session_commandline("c:\\system32\\cmd.exe", NULL, NULL, 1); + ASSERT_STRING_EQ(out + shellhost_path_len + 1, "\"c:\\system32\\cmd.exe\""); + out[shellhost_path_len] = '\0'; + ASSERT_STRING_EQ(out, shellhost_path); + 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", 0); + 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", 0); + 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", 0); + 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", 0); + buf[len_pg] = '\0'; + strcat(buf, "\\scp.exe\" -arg"); + ASSERT_STRING_EQ(out, buf); + out = build_session_commandline("c:\\system32\\cmd.exe", NULL, "mycommand -arg", 1); + ASSERT_STRING_EQ(out + shellhost_path_len + 1, "\"c:\\system32\\cmd.exe\" /c mycommand -arg"); + out[shellhost_path_len] = '\0'; + ASSERT_STRING_EQ(out, shellhost_path); + free(out); + + TEST_DONE(); + + TEST_START("wsl bash shell tests"); + out = build_session_commandline("c:\\system32\\bash.exe", NULL, "internal-sftp -arg", 0); + 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", 0); + 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", 0); + ASSERT_STRING_EQ(out, "\"c:\\system32\\bash\" -c \"sftp-server.exe -arg\""); + free(out); + out = build_session_commandline("c:\\system32\\bash", NULL, "scP -arg", 0); + ASSERT_STRING_EQ(out, "\"c:\\system32\\bash\" -c \"scp.exe -arg\""); + free(out); + out = build_session_commandline("c:\\system32\\bash", "-custom", "mycommand -arg", 1); + ASSERT_STRING_EQ(out + shellhost_path_len + 1, "\"c:\\system32\\bash\" -custom \"mycommand -arg\""); + out[shellhost_path_len] = '\0'; + ASSERT_STRING_EQ(out, shellhost_path); + free(out); + TEST_DONE(); + + TEST_START("cygwin bash shell tests"); + out = build_session_commandline("c:\\cygwin\\bash.exe", NULL, "internal-sftp -arg", 0); + 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", 0); + 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", 0); + ASSERT_STRING_EQ(out, "\"c:\\cygwin\\bash\" -c \"sftp-server.exe -arg\""); + free(out); + out = build_session_commandline("c:\\cygwin\\bash", NULL, "sCp -arg", 0); + ASSERT_STRING_EQ(out, "\"c:\\cygwin\\bash\" -c \"scp.exe -arg\""); + free(out); + out = build_session_commandline("c:\\cygwin\\bash", "-custom", "mycommand -arg", 1); + ASSERT_STRING_EQ(out + shellhost_path_len + 1, "\"c:\\cygwin\\bash\" -custom \"mycommand -arg\""); + out[shellhost_path_len] = '\0'; + ASSERT_STRING_EQ(out, shellhost_path); + free(out); + TEST_DONE(); + + TEST_START("powershell shell tests"); + out = build_session_commandline("c:\\powershell.exe", NULL, "internal-sftp -arg", 0); + ASSERT_STRING_EQ(out, "\"c:\\powershell.exe\" -c sftp-server.exe -arg"); + free(out); + out = build_session_commandline("c:\\powershell", NULL, "sftp-server -arg", 0); + ASSERT_STRING_EQ(out, "\"c:\\powershell\" -c sftp-server.exe -arg"); + free(out); + out = build_session_commandline("c:\\powershell.exe", NULL, "sftp-sERver.exe -arg", 0); + ASSERT_STRING_EQ(out, "\"c:\\powershell.exe\" -c sftp-server.exe -arg"); + free(out); + out = build_session_commandline("c:\\powershell.exe", NULL, "scP -arg", 0); + ASSERT_STRING_EQ(out, "\"c:\\powershell.exe\" -c scp.exe -arg"); + free(out); + out = build_session_commandline("c:\\powershell.exe", "-custom", "mycommand -arg", 1); + ASSERT_STRING_EQ(out + shellhost_path_len + 1, "\"c:\\powershell.exe\" -custom mycommand -arg"); + out[shellhost_path_len] = '\0'; + ASSERT_STRING_EQ(out, shellhost_path); + free(out); + TEST_DONE(); + + + TEST_START("other shell tests"); + out = build_session_commandline("c:\\myshell.exe", NULL, "internal-sftp -arg", 0); + ASSERT_STRING_EQ(out, "\"c:\\myshell.exe\" -c sftp-server.exe -arg"); + free(out); + out = build_session_commandline("c:\\myshell", NULL, "sftp-server -arg", 0); + ASSERT_STRING_EQ(out, "\"c:\\myshell\" -c sftp-server.exe -arg"); + free(out); + out = build_session_commandline("c:\\myshell", NULL, "sftp-seRVer.exe -arg", 0); + ASSERT_STRING_EQ(out, "\"c:\\myshell\" -c sftp-server.exe -arg"); + free(out); + out = build_session_commandline("c:\\myshell", NULL, "sCp -arg", 0); + ASSERT_STRING_EQ(out, "\"c:\\myshell\" -c scp.exe -arg"); + free(out); + out = build_session_commandline("c:\\myshell", "-custom", "mycommand -arg", 1); + ASSERT_STRING_EQ(out + shellhost_path_len + 1, "\"c:\\myshell\" -custom mycommand -arg"); + out[shellhost_path_len] = '\0'; + ASSERT_STRING_EQ(out, shellhost_path); + free(out); + TEST_DONE(); +} + + void miscellaneous_tests() { @@ -321,4 +452,5 @@ miscellaneous_tests() test_realpath(); test_statvfs(); test_chroot(); + test_build_session_commandline(); } diff --git a/session.c b/session.c index 682e0e25d..1b8cdeaca 100644 --- a/session.c +++ b/session.c @@ -468,13 +468,15 @@ setup_session_vars(Session* s) wchar_t *pw_dir_w = NULL, *tmp = NULL; char buf[256]; wchar_t wbuf[256]; - char* laddr; + char *laddr, *c; int ret = -1; struct ssh *ssh = active_state; /* XXX */ UTF8_TO_UTF16_WITH_CLEANUP(pw_dir_w, s->pw->pw_dir); - UTF8_TO_UTF16_WITH_CLEANUP(tmp, s->pw->pw_name); + /* skip domain part (if there) 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->display) { UTF8_TO_UTF16_WITH_CLEANUP(tmp, s->display); @@ -527,15 +529,15 @@ cleanup: return ret; } -char* w32_programdir(); int register_child(void* child, unsigned long pid); +char* build_session_commandline(const char *, const char *, const char *, int); 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 *progdir = w32_programdir(); wchar_t *exec_command_w = NULL; - char *command_enhanced = NULL, *exec_command = 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) @@ -568,119 +570,12 @@ int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) { if (!in_chroot) chdir(s->pw->pw_dir); -#define CMDLINE_APPEND(P, S) \ -do { \ - int _S_len = strlen(S); \ - memcpy((P), (S), _S_len); \ - (P) += _S_len; \ -} while(0) + if (s->is_subsystem >= SUBSYSTEM_INT_SFTP_ERROR) + command = "echo This service allows sftp connections only."; - /* special cases where incoming command needs to be adjusted */ - do { - if (s->is_subsystem >= SUBSYSTEM_INT_SFTP_ERROR) { - command = "echo This service allows sftp connections only."; - break; - } - - /* if scp or sftp - add module path if command is not absolute */ - if (s->is_subsystem || (command && memcmp(command, "scp", 3) == 0)) { - int en_size; - char* p; - - if (!command || command[0] == '\0') { - error("expecting command for subsystem or scp"); - errno = EOTHER; - return -1; - } - - /* if absolute skip further logic */ - if (command[1] == ':') - break; - - /* account for max possible enhanced path */ - en_size = PATH_MAX + 1 + strlen(command) ; - if ((command_enhanced = malloc(en_size)) == NULL) { - errno = ENOMEM; - goto cleanup; - } - - p = command_enhanced; - CMDLINE_APPEND(p, progdir); - CMDLINE_APPEND(p, "\\"); - - /* since Windows does not support fork, launch sftp-server.exe for internal_sftp */ - if (IS_INTERNAL_SFTP(command)) { - CMDLINE_APPEND(p, "sftp-server.exe"); - /* add subsystem arguments if any */ - CMDLINE_APPEND(p, command + strlen(INTERNAL_SFTP_NAME)); - } else - CMDLINE_APPEND(p, command); - - *p = '\0'; - - command = command_enhanced; - break; - } - } while (0); - - /* build command line to be executed */ - { - /* max possible cmdline size - account for shellhost path, shell path and command */ - int max_cmdline_size = 2 * PATH_MAX + (command ? strlen(command) + 1 : 1) + 1; - char* p; - enum sh_type { SH_CMD, SH_PS, SH_BASH, SH_OTHER } shell_type = SH_OTHER; - extern char* shell_command_option; - - if ((exec_command = malloc(max_cmdline_size)) == NULL) { - errno = ENOMEM; - goto cleanup; - } - - p = exec_command; - - if (strstr(s->pw->pw_shell, "system32\\cmd.exe")) - shell_type = SH_CMD; - else if (strstr(s->pw->pw_shell, "powershell")) - shell_type = SH_PS; - else if (strstr(s->pw->pw_shell, "bash")) - shell_type = SH_BASH; - else if (strstr(s->pw->pw_shell, "cygwin")) - shell_type = SH_BASH; - - /* build command line */ - /* For PTY - launch via ssh-shellhost.exe */ - if (pty) { - CMDLINE_APPEND(p,"\""); - CMDLINE_APPEND(p, progdir); - CMDLINE_APPEND(p, "\\ssh-shellhost.exe\" "); - } - - /* Add shell */ - CMDLINE_APPEND(p, "\""); - CMDLINE_APPEND(p, s->pw->pw_shell); - CMDLINE_APPEND(p, "\""); - - /* Add command option and command*/ - if (command) { - if (shell_command_option) { - CMDLINE_APPEND(p, " "); - CMDLINE_APPEND(p, shell_command_option); - CMDLINE_APPEND(p, " "); - } else if (shell_type == SH_CMD) - CMDLINE_APPEND(p, " /c "); - else - CMDLINE_APPEND(p, " -c "); - - if (shell_type == SH_BASH) - CMDLINE_APPEND(p, "\""); - - CMDLINE_APPEND(p, command); - - if (shell_type == SH_BASH) - CMDLINE_APPEND(p, "\""); - } - *p = '\0'; - } + exec_command = build_session_commandline(s->pw->pw_shell, shell_command_option, command, pty); + if (exec_command == NULL) + goto cleanup; /* start the process */ { @@ -760,8 +655,6 @@ do { \ ret = 0; cleanup: - if (!command_enhanced) - free(command_enhanced); if (!exec_command) free(exec_command); if (!exec_command_w)