From 84e87be8ae88bbd038b7f1427383d5c3b398a69d Mon Sep 17 00:00:00 2001 From: Manoj Ampalam Date: Wed, 25 Jul 2018 15:15:05 -0700 Subject: [PATCH] ConPTY changes and support for auto-updating known_hosts; - Logic to support conpty (currently disabled until validation is complete) - fdopen() and fchmod() support for file handles - support for auto updating known_hosts via ssh and ssh-keygen - Support for dynamic Windows-size changes with PTY - Changes to support OneCore SDK - Test cases --- contrib/win32/openssh/vstsbuild.ps1 | 4 +- contrib/win32/openssh/win32iocompat.vcxproj | 1 + contrib/win32/win32compat/console.c | 47 ++++- contrib/win32/win32compat/fileio.c | 80 +++++---- contrib/win32/win32compat/inc/sys/stat.h | 4 + contrib/win32/win32compat/misc.c | 55 +----- contrib/win32/win32compat/misc_internal.h | 20 ++- contrib/win32/win32compat/shell-host.c | 163 ++++++++++++++++-- contrib/win32/win32compat/signal.c | 2 +- contrib/win32/win32compat/tncon.h | 2 +- contrib/win32/win32compat/tnnet.c | 31 ++-- contrib/win32/win32compat/tnnet.h | 2 +- contrib/win32/win32compat/w32api_proxies.c | 118 ++++++++++++- contrib/win32/win32compat/w32api_proxies.h | 4 +- contrib/win32/win32compat/w32fd.c | 67 +++++-- contrib/win32/win32compat/win32_pty.c | 120 +++++++++++++ contrib/win32/win32compat/win32_sshpty.c | 34 +++- .../win32/win32compat/win32_usertoken_utils.c | 31 +++- contrib/win32/win32compat/wmain_sshd.c | 8 +- hostfile.c | 6 - regress/pesterTests/KeyUtils.Tests.ps1 | 24 +++ regress/pesterTests/SSH.Tests.ps1 | 124 +++---------- regress/pesterTests/ShellHost.Tests.ps1 | 42 +++++ .../win32compat/miscellaneous_tests.c | 106 ++++-------- session.c | 34 ++-- ssh-keygen.c | 35 +--- sshd.c | 4 + 27 files changed, 767 insertions(+), 401 deletions(-) create mode 100644 contrib/win32/win32compat/win32_pty.c create mode 100644 regress/pesterTests/ShellHost.Tests.ps1 diff --git a/contrib/win32/openssh/vstsbuild.ps1 b/contrib/win32/openssh/vstsbuild.ps1 index 64a75b32c..6c771a3a1 100644 --- a/contrib/win32/openssh/vstsbuild.ps1 +++ b/contrib/win32/openssh/vstsbuild.ps1 @@ -18,7 +18,7 @@ if (-not $gitBinFullPath) function Get-RepoFork { [CmdletBinding()] - param([string]$AccountURL, [string]$RepoFork, [string]$repoLocalPath, [string]$BranchName) + param([string]$AccountURL="https://github.com/powershell", [string]$RepoFork, [string]$repoLocalPath, [string]$BranchName) if (Test-Path -Path $repoLocalPath -PathType Container) { Remove-Item -Path $repoLocalPath -Recurse -Force @@ -132,4 +132,4 @@ catch finally{ Write-VstsTaskState exit 0 -} \ No newline at end of file +} diff --git a/contrib/win32/openssh/win32iocompat.vcxproj b/contrib/win32/openssh/win32iocompat.vcxproj index d4b351321..86b4688bd 100644 --- a/contrib/win32/openssh/win32iocompat.vcxproj +++ b/contrib/win32/openssh/win32iocompat.vcxproj @@ -304,6 +304,7 @@ + diff --git a/contrib/win32/win32compat/console.c b/contrib/win32/win32compat/console.c index a21609e40..e25bf9983 100644 --- a/contrib/win32/win32compat/console.c +++ b/contrib/win32/win32compat/console.c @@ -40,11 +40,14 @@ #include "debug.h" #include "console.h" #include "ansiprsr.h" +#include "misc_internal.h" HANDLE hOutputConsole = NULL; DWORD stdin_dwSavedAttributes = 0; DWORD stdout_dwSavedAttributes = 0; WORD wStartingAttributes = 0; +unsigned int console_out_cp_saved = 0; +unsigned int console_in_cp_saved = 0; int ScreenX; int ScreenY; @@ -142,14 +145,28 @@ ConEnterRawMode() SavedViewRect = csbi.srWindow; debug("console doesn't support the ansi parsing"); } else { - ConSaveViewRect(); debug("console supports the ansi parsing"); - } + if (is_conpty_supported()) { + console_out_cp_saved = GetConsoleOutputCP(); + console_in_cp_saved = GetConsoleCP(); + if (SetConsoleOutputCP(CP_UTF8)) + debug3("Successfully set console output code page from:%d to %d", console_out_cp_saved, CP_UTF8); + else + error("Failed to set console output code page from:%d to %d error:%d", console_out_cp_saved, CP_UTF8, GetLastError()); + + if (SetConsoleCP(CP_UTF8)) + debug3("Successfully set console input code page from:%d to %d", console_in_cp_saved, CP_UTF8); + else + error("Failed to set console input code page from:%d to %d error:%d", console_in_cp_saved, CP_UTF8, GetLastError()); + } else { + ConSaveViewRect(); + } + } ConSetScreenX(); ConSetScreenY(); ScrollTop = 0; - ScrollBottom = ConVisibleWindowHeight(); + ScrollBottom = ConVisibleWindowHeight(); in_raw_mode = 1; } @@ -160,6 +177,22 @@ ConExitRawMode() { SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), stdin_dwSavedAttributes); SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), stdout_dwSavedAttributes); + + if (FALSE == isAnsiParsingRequired && is_conpty_supported()) { + if (console_out_cp_saved) { + if(SetConsoleOutputCP(console_out_cp_saved)) + debug3("Successfully set console output code page from %d to %d", CP_UTF8, console_out_cp_saved); + else + error("Failed to set console output code page from %d to %d error:%d", CP_UTF8, console_out_cp_saved, GetLastError()); + } + + if (console_in_cp_saved) { + if (SetConsoleCP(console_in_cp_saved)) + debug3("Successfully set console input code page from %d to %d", CP_UTF8, console_in_cp_saved); + else + error("Failed to set console input code page from %d to %d error:%d", CP_UTF8, console_in_cp_saved, GetLastError()); + } + } } /* Used to exit the raw mode */ @@ -223,7 +256,7 @@ ConSetScreenRect(int xSize, int ySize) bSuccess = SetConsoleScreenBufferSize(hOutputConsole, coordScreen); } - if (bSuccess) + if (bSuccess && !is_conpty_supported()) ConSaveViewRect(); /* if the current buffer *is* the size we want, don't do anything! */ @@ -270,7 +303,7 @@ ConSetScreenSize(int xSize, int ySize) bSuccess = SetConsoleWindowInfo(hOutputConsole, TRUE, &srWindowRect); } - if (bSuccess) + if (bSuccess && !is_conpty_supported()) ConSaveViewRect(); /* if the current buffer *is* the size we want, don't do anything! */ @@ -1490,7 +1523,6 @@ void ConSaveViewRect() { CONSOLE_SCREEN_BUFFER_INFO csbi; - if (GetConsoleScreenBufferInfo(hOutputConsole, &csbi)) SavedViewRect = csbi.srWindow; } @@ -1590,7 +1622,8 @@ ConMoveCursorTopOfVisibleWindow() offset = csbi.dwCursorPosition.Y - csbi.srWindow.Top; ConMoveVisibleWindow(offset); - ConSaveViewRect(); + if(!is_conpty_supported()) + ConSaveViewRect(); } } diff --git a/contrib/win32/win32compat/fileio.c b/contrib/win32/win32compat/fileio.c index ec5548236..8c5b58f49 100644 --- a/contrib/win32/win32compat/fileio.c +++ b/contrib/win32/win32compat/fileio.c @@ -207,7 +207,7 @@ fileio_pipe(struct w32_io* pio[2], int duplex) sec_attributes.bInheritHandle = TRUE; sec_attributes.lpSecurityDescriptor = NULL; - sec_attributes.nLength = 0; + sec_attributes.nLength = sizeof(sec_attributes); /* create named pipe */ write_handle = CreateNamedPipeA(pipe_name, @@ -415,18 +415,18 @@ cleanup: /* returns 1 if true, 0 otherwise */ int -file_in_chroot_jail(HANDLE handle, const char* path_utf8) { +file_in_chroot_jail(HANDLE handle) { /* ensure final path is within chroot */ - wchar_t path_buf[MAX_PATH], *final_path; - if (GetFinalPathNameByHandleW(handle, path_buf, MAX_PATH, 0) == 0) { - debug3("failed to get final path of file:%s error:%d", path_utf8, GetLastError()); + wchar_t *final_path; + + final_path = get_final_path_by_handle(handle); + if (!final_path) return 0; - } - final_path = path_buf + 4; + to_wlower_case(final_path); if ((wcslen(final_path) < wcslen(chroot_pathw)) || - memcmp(final_path, chroot_pathw, 2 * wcslen(chroot_pathw)) != 0 || - final_path[wcslen(chroot_pathw)] != '\\') { + memcmp(final_path, chroot_pathw, 2 * wcslen(chroot_pathw)) != 0 || + final_path[wcslen(chroot_pathw)] != '\\') { debug3("access denied due to attempt to escape chroot jail"); return 0; } @@ -478,7 +478,8 @@ fileio_open(const char *path_utf8, int flags, mode_t mode) goto cleanup; } - if (chroot_pathw && !nonfs_dev && !file_in_chroot_jail(handle, path_utf8)) { + if (chroot_pathw && !nonfs_dev && !file_in_chroot_jail(handle)) { + debug3("%s is not in chroot jail", path_utf8); errno = EACCES; goto cleanup; } @@ -776,7 +777,7 @@ fileio_fstat(struct w32_io* pio, struct _stat64 *buf) return -1; } - int fd = _open_osfhandle(dup_handle, 0); + int fd = _open_osfhandle((intptr_t)dup_handle, 0); debug4("fstat - pio:%p", pio); if (fd == -1) { CloseHandle(dup_handle); @@ -902,44 +903,41 @@ fileio_lseek(struct w32_io* pio, unsigned __int64 offset, int origin) return 0; } -/* fdopen implementation */ +/* + * fdopen implementation - use with caution + * this implementation deviates from POSIX spec the following way + * - the underlying file descriptor is closed automatically + * hence no further POSIX io operations (read, write, close, etc) on the + * underlying file descriptor are supported + */ FILE* fileio_fdopen(struct w32_io* pio, const char *mode) { - int fd_flags = 0; + wchar_t *file_path, *wmode = NULL; + FILE* ret = NULL; + debug4("fdopen - io:%p", pio); - /* logic below doesn't work with overlapped file HANDLES */ - if (mode[1] == '\0') { - switch (*mode) { - case 'r': - fd_flags = _O_RDONLY; - break; - case 'w': - break; - case 'a': - fd_flags = _O_APPEND; - break; - default: - errno = ENOTSUP; - debug3("fdopen - ERROR unsupported mode %s", mode); - return NULL; - } - } else { - errno = ENOTSUP; - debug3("fdopen - ERROR unsupported mode %s", mode); - return NULL; - } + if ((wmode = utf8_to_utf16(mode)) == NULL) + goto cleanup; - int fd = _open_osfhandle((intptr_t)pio->handle, fd_flags); + file_path = get_final_path_by_handle(pio->handle); + if (!file_path) + goto cleanup; + + /* + * close the win32 handle right away and remove entry from table + * otherwise, wfopen will get an access denied due to sharing violation + */ + int w32_close(int); + w32_close(pio->table_index); + errno = _wfopen_s(&ret, file_path, wmode); - if (fd == -1) { - errno = EOTHER; - debug3("fdopen - ERROR:%d _open_osfhandle()", errno); - return NULL; - } +cleanup: + if (wmode) + free(wmode); - return _fdopen(fd, mode); + return ret; } void diff --git a/contrib/win32/win32compat/inc/sys/stat.h b/contrib/win32/win32compat/inc/sys/stat.h index e8e960fb5..5e654fa70 100644 --- a/contrib/win32/win32compat/inc/sys/stat.h +++ b/contrib/win32/win32compat/inc/sys/stat.h @@ -38,6 +38,10 @@ int w32_mkdir(const char *pathname, unsigned short mode); int w32_chmod(const char *, mode_t); #define chmod w32_chmod +int w32_fchmod(int fd, mode_t mode); +#define fchmod w32_fchmod + + struct w32_stat { dev_t st_dev; /* ID of device containing file */ unsigned short st_ino; /* inode number */ diff --git a/contrib/win32/win32compat/misc.c b/contrib/win32/win32compat/misc.c index e1da03ad6..76e79a003 100644 --- a/contrib/win32/win32compat/misc.c +++ b/contrib/win32/win32compat/misc.c @@ -286,7 +286,8 @@ w32_fopen_utf8(const char *input_path, const char *mode) if (chroot_pathw && !nonfs_dev) { /* ensure final path is within chroot */ HANDLE h = (HANDLE)_get_osfhandle(_fileno(f)); - if (!file_in_chroot_jail(h, input_path)) { + if (!file_in_chroot_jail(h)) { + debug3("%s is not in chroot jail", input_path); fclose(f); f = NULL; errno = EACCES; @@ -419,34 +420,6 @@ w32_setvbuf(FILE *stream, char *buffer, int mode, size_t size) { return setvbuf(stream, buffer, mode, size); } -/* TODO - deprecate this. This is not a POSIX API, used internally only */ -char * -w32_programdir() -{ - wchar_t* wpgmptr; - - if (s_programdir != NULL) - return s_programdir; - - if (_get_wpgmptr(&wpgmptr) != 0) - return NULL; - - if ((s_programdir = utf16_to_utf8(wpgmptr)) == NULL) - return NULL; - - /* null terminate after directory path */ - char* tail = s_programdir + strlen(s_programdir); - while (tail > s_programdir && *tail != '\\' && *tail != '/') - tail--; - - if (tail > s_programdir) - *tail = '\0'; - else - *tail = '.'; /* current directory */ - - return s_programdir; -} - int daemon(int nochdir, int noclose) { @@ -1592,11 +1565,11 @@ cleanup: /* 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) +build_session_commandline(const char *shell, const char* shell_arg, const char *command) { enum sh_type { SH_OTHER, SH_CMD, SH_PS, SH_BASH } 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; + char *progdir = __progdir, *cmd_sp = NULL, *cmdline = NULL, *ret = NULL, *p; int len, progdir_len = (int)strlen(progdir); #define CMDLINE_APPEND(P, S) \ @@ -1650,7 +1623,7 @@ do { \ 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; @@ -1687,7 +1660,6 @@ do { \ p = cmd_sp; if (shell_type == SH_CMD) { - CMDLINE_APPEND(p, "\""); CMDLINE_APPEND(p, progdir); @@ -1709,8 +1681,6 @@ do { \ } 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 */ @@ -1723,11 +1693,6 @@ do { \ } 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, "\""); @@ -1742,14 +1707,10 @@ do { \ else CMDLINE_APPEND(p, " -c "); - /* bash type shells require " decoration around command*/ - if (shell_type == SH_BASH) - CMDLINE_APPEND(p, "\""); - + /* Add double quotes around command */ + CMDLINE_APPEND(p, "\""); CMDLINE_APPEND(p, command); - - if (shell_type == SH_BASH) - CMDLINE_APPEND(p, "\""); + CMDLINE_APPEND(p, "\""); } *p = '\0'; ret = cmdline; diff --git a/contrib/win32/win32compat/misc_internal.h b/contrib/win32/win32compat/misc_internal.h index 426727c24..966271359 100644 --- a/contrib/win32/win32compat/misc_internal.h +++ b/contrib/win32/win32compat/misc_internal.h @@ -25,6 +25,17 @@ /* maximum size for user principal name as defined in ad schema */ #define MAX_UPN_LEN 1024 +/* PTY windows size event type (for conhost and ssh-shellhost) */ +#define PTY_SIGNAL_RESIZE_WINDOW 8u + +/* maximum command line length */ +#define MAX_CMD_LEN 8191 + +/* prog paths */ +extern char* __progname; +extern char* __progdir; +extern wchar_t* __wprogdir; + static char *machine_domain_name; extern char* chroot_path; @@ -35,7 +46,7 @@ extern wchar_t* chroot_pathw; wchar_t * resolved_path_utf16(const char *); void w32posix_initialize(); void w32posix_done(); -char* w32_programdir(); +void init_prog_paths(); void convertToBackslash(char *str); void convertToBackslashW(wchar_t *str); void convertToForwardslash(char *str); @@ -51,9 +62,12 @@ HANDLE get_user_token(const char* user, int impersonation); int load_user_profile(HANDLE user_token, char* user); int create_directory_withsddl(wchar_t *path, wchar_t *sddl); int is_absolute_path(const char *); -int file_in_chroot_jail(HANDLE, 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 ); +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); 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/shell-host.c b/contrib/win32/win32compat/shell-host.c index 02a85e8a6..d8fdc3182 100644 --- a/contrib/win32/win32compat/shell-host.c +++ b/contrib/win32/win32compat/shell-host.c @@ -44,7 +44,6 @@ #define MAX_CONSOLE_COLUMNS 9999 #define MAX_CONSOLE_ROWS 9999 -#define MAX_CMD_LEN 8191 // msdn #define WM_APPEXIT WM_USER+1 #define MAX_EXPECTED_BUFFER_SIZE 1024 /* 4KB is the largest size for which writes are guaranteed to be atomic */ @@ -274,13 +273,14 @@ HANDLE child_in = INVALID_HANDLE_VALUE; HANDLE child_err = INVALID_HANDLE_VALUE; HANDLE pipe_in = INVALID_HANDLE_VALUE; HANDLE pipe_out = INVALID_HANDLE_VALUE; -HANDLE pipe_err = INVALID_HANDLE_VALUE; +HANDLE pipe_ctrl = INVALID_HANDLE_VALUE; HANDLE child = INVALID_HANDLE_VALUE; HANDLE job = NULL; HANDLE hConsoleBuffer = INVALID_HANDLE_VALUE; HANDLE monitor_thread = INVALID_HANDLE_VALUE; HANDLE io_thread = INVALID_HANDLE_VALUE; HANDLE ux_thread = INVALID_HANDLE_VALUE; +HANDLE ctrl_thread = INVALID_HANDLE_VALUE; DWORD child_exit_code = 0; DWORD hostProcessId = 0; @@ -800,6 +800,48 @@ MonitorChild(_In_ LPVOID lpParameter) return 0; } +unsigned __stdcall +ControlThread(LPVOID p) +{ + short type, row, col; + DWORD len; + COORD coord; + SMALL_RECT rect; + while (1) { + if (!ReadFile(pipe_ctrl, &type, 2, &len, NULL)) + break; + if (type != PTY_SIGNAL_RESIZE_WINDOW) + break; + if (!ReadFile(pipe_ctrl, &col, 2, &len, NULL)) + break; + if (!ReadFile(pipe_ctrl, &row, 2, &len, NULL)) + break; + + /* + * when reducing width, console seemed to retain prior width + * while increasing width, however, it behaves right + * + * hence setting it less by 1 and setting it again to the right + * count + */ + + coord.X = col - 1; + coord.Y = row; + rect.Top = 0; + rect.Left = 0; + rect.Bottom = row - 1; + rect.Right = col - 2; + SetConsoleScreenBufferSize(child_out, coord); + SetConsoleWindowInfo(child_out, TRUE, &rect); + + coord.X = col; + rect.Right = col - 1; + SetConsoleScreenBufferSize(child_out, coord); + SetConsoleWindowInfo(child_out, TRUE, &rect); + } + return 0; +} + DWORD ProcessEvent(void *p) { @@ -1231,10 +1273,10 @@ start_with_pty(wchar_t *command) pipe_in = GetStdHandle(STD_INPUT_HANDLE); pipe_out = GetStdHandle(STD_OUTPUT_HANDLE); - pipe_err = GetStdHandle(STD_ERROR_HANDLE); + pipe_ctrl = GetStdHandle(STD_ERROR_HANDLE); /* copy pipe handles passed through std io*/ - if ((pipe_in == INVALID_HANDLE_VALUE) || (pipe_out == INVALID_HANDLE_VALUE) || (pipe_err == INVALID_HANDLE_VALUE)) + if ((pipe_in == INVALID_HANDLE_VALUE) || (pipe_out == INVALID_HANDLE_VALUE) || (pipe_ctrl == INVALID_HANDLE_VALUE)) return -1; cp = GetConsoleCP(); @@ -1272,11 +1314,8 @@ start_with_pty(wchar_t *command) /* * Launch via cmd.exe /c, otherwise known issues exist with color rendering in powershell */ - cmd[0] = L'\0'; - GOTO_CLEANUP_ON_ERR(wcscat_s(cmd, MAX_CMD_LEN, system32_path)); - GOTO_CLEANUP_ON_ERR(wcscat_s(cmd, MAX_CMD_LEN, L"\\cmd.exe /c ")); - GOTO_CLEANUP_ON_ERR(wcscat_s(cmd, MAX_CMD_LEN, command)); - + _snwprintf_s(cmd, MAX_CMD_LEN, MAX_CMD_LEN, L"\"%ls\\cmd.exe\" /c \"%ls\"", system32_path, command); + SetConsoleCtrlHandler(NULL, FALSE); GOTO_CLEANUP_ON_FALSE(CreateProcess(NULL, cmd, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)); @@ -1311,6 +1350,10 @@ start_with_pty(wchar_t *command) if (IS_INVALID_HANDLE(ux_thread)) goto cleanup; + ctrl_thread = (HANDLE)_beginthreadex(NULL, 0, ControlThread, NULL, 0, NULL); + if (IS_INVALID_HANDLE(ctrl_thread)) + goto cleanup; + ProcessMessages(NULL); cleanup: dwStatus = GetLastError(); @@ -1330,6 +1373,11 @@ cleanup: CloseHandle(io_thread); } + if (!IS_INVALID_HANDLE(ctrl_thread)) { + TerminateThread(ctrl_thread, 0); + CloseHandle(ctrl_thread); + } + if (hEventHook) __UnhookWinEvent(hEventHook); @@ -1349,20 +1397,99 @@ cleanup: return child_exit_code; } -int -wmain(int ac, wchar_t **av) +/* implements a basic shell - launches given cmd using CreateProcess */ +int start_as_shell(wchar_t* cmd) { - wchar_t *exec_command; + STARTUPINFOW si; + PROCESS_INFORMATION pi; - _set_invalid_parameter_handler(my_invalid_parameter_handler); + memset(&si, 0, sizeof(STARTUPINFOW)); + memset(&pi, 0, sizeof(PROCESS_INFORMATION)); + si.cb = sizeof(STARTUPINFOW); - if (ac == 1) { - printf("usage: shellhost.exe \n"); + if (CreateProcessW(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi) == FALSE) { + printf("ssh-shellhost cannot run '%ls', error: %d", cmd, GetLastError()); exit(255); } - /* get past shellhost.exe in commandline */ - exec_command = wcsstr(GetCommandLineW(), L"shellhost.exe") + wcslen(L"shellhost.exe") + 1; + CloseHandle(pi.hThread); + /* close std io handles */ + CloseHandle(GetStdHandle(STD_INPUT_HANDLE)); + CloseHandle(GetStdHandle(STD_OUTPUT_HANDLE)); + CloseHandle(GetStdHandle(STD_ERROR_HANDLE)); + child_exit_code = 255; - return start_with_pty(exec_command); + /* wait for child to exit */ + WaitForSingleObject(pi.hProcess, INFINITE); + + if (!GetExitCodeProcess(pi.hProcess, &child_exit_code)) + printf("ssh-shellhost unable to track child process, error: %d", GetLastError()); + + CloseHandle(pi.hProcess); + return child_exit_code; +} + +/* + * Usage: + * Execute commandline with PTY + * ssh-shellhost.exe ---pty commandline + * Note that in PTY mode, stderr is taken as the control channel + * to receive Windows size change events + * + * Execute commandline like shell (plain IO redirection) + * Syntax mimics cmd.exe -c usage. Note the explicit double quotes + * around actual commandline to execute. + * ssh-shellhost.exe -c "commandline" + * Ex. ssh-shellhost.exe -c "notepad.exe file.txt" + * ssh-shellhost.exe -c ""my program.exe" "arg 1" "arg 2"" + */ +int +wmain(int ac, wchar_t **av) +{ + wchar_t *exec_command, *option, *cmdline; + int with_pty, len; + + _set_invalid_parameter_handler(my_invalid_parameter_handler); + + if (ac == 1) + goto usage; + + if ((cmdline = _wcsdup(GetCommandLineW())) == NULL) { + printf("ssh-shellhost.exe ran out of memory"); + exit(255); + } + + if (option = wcsstr(cmdline, L" ---pty ")) + with_pty = 1; + else if (option = wcsstr(cmdline, L" -c ")) + with_pty = 0; + else + goto usage; + + if (with_pty) + exec_command = option + wcslen(L" ---pty "); + else + exec_command = option + wcslen(L" -c "); + + /* strip preceding white spaces */ + while (*exec_command != L'\0' && *exec_command == L' ') + exec_command++; + + if (exec_command == L'\0') + goto usage; + + if (with_pty) + return start_with_pty(exec_command); + else { + /* if commandline is enclosed in double quotes, remove them */ + len = (int)wcslen(exec_command); + if (len > 2 && *exec_command == L'\"' && *(exec_command + len - 1) == L'\"') { + *(exec_command + len - 1) = L'\0'; + exec_command++; + } + return start_as_shell(exec_command); + } +usage: + printf("ssh-shellhost does not support command line: %ls", cmdline); + exit(255); } diff --git a/contrib/win32/win32compat/signal.c b/contrib/win32/win32compat/signal.c index 0776f2350..5fbbd7e19 100644 --- a/contrib/win32/win32compat/signal.c +++ b/contrib/win32/win32compat/signal.c @@ -107,7 +107,7 @@ static VOID CALLBACK sigwinch_APCProc(_In_ ULONG_PTR dwParam) { debug5("SIGTERM APCProc()"); - sigaddset(&pending_signals, W32_SIGWINCH); + sigaddset(&pending_signals, SIGWINCH); } void diff --git a/contrib/win32/win32compat/tncon.h b/contrib/win32/win32compat/tncon.h index d58f596db..6b625d73d 100644 --- a/contrib/win32/win32compat/tncon.h +++ b/contrib/win32/win32compat/tncon.h @@ -56,7 +56,7 @@ #define SHIFT_TAB_KEY "\x1b[~" #define SHIFT_ALT_Q "\x1b?" #define ESCAPE_KEY "\x1b" -#define BACKSPACE_KEY "\b" +#define BACKSPACE_KEY "\x7f" // VT100 Function Key's #define VT100_PF1_KEY "\x1bO2" diff --git a/contrib/win32/win32compat/tnnet.c b/contrib/win32/win32compat/tnnet.c index 244e72667..8c3cc8b00 100644 --- a/contrib/win32/win32compat/tnnet.c +++ b/contrib/win32/win32compat/tnnet.c @@ -37,6 +37,7 @@ #include "ansiprsr.h" #include "inc\utf.h" #include "console.h" +#include "misc_internal.h" #define dwBuffer 4096 @@ -51,16 +52,17 @@ BOOL isFirstPacket = TRUE; * are hardcoded in the server and will be transformed to Windows Console commands. */ void -processBuffer(HANDLE handle, char *buf, size_t len, unsigned char **respbuf, size_t *resplen) +processBuffer(HANDLE handle, char *buf, DWORD len, unsigned char **respbuf, size_t *resplen) { unsigned char *pszNewHead = NULL; unsigned char *pszHead = NULL; unsigned char *pszTail = NULL; const char *applicationModeSeq = "\x1b[?1h"; - const int applicationModeSeqLen = (int)strlen(applicationModeSeq); + const DWORD applicationModeSeqLen = (DWORD)strlen(applicationModeSeq); const char *normalModeSeq = "\x1b[?1l"; - const int normalModeSeqLen = (int)strlen(normalModeSeq); + const DWORD normalModeSeqLen = (DWORD)strlen(normalModeSeq); const char *clsSeq = "\x1b[2J"; + static int track_view_port = 1; if (len == 0) return; @@ -68,6 +70,10 @@ processBuffer(HANDLE handle, char *buf, size_t len, unsigned char **respbuf, siz if (false == isAnsiParsingRequired) { if(isFirstPacket) { isFirstPacket = FALSE; + + if (is_conpty_supported()) + track_view_port = 0; + /* Windows server at first sends the "cls" after the connection is established. * There is a bug in the conhost which causes the visible window data to loose so to * mitigate that issue we need to first move the visible window so that the cursor is at the top of the visible window. @@ -81,15 +87,18 @@ processBuffer(HANDLE handle, char *buf, size_t len, unsigned char **respbuf, siz else if(len >= normalModeSeqLen && strstr(buf, normalModeSeq)) gbVTAppMode = false; + /* WriteFile() gets messy when user does scroll up/down so we need to restore the visible window. + * It's a conhost bug but we need to live with it as they are not going to back port the fix. + */ + if(track_view_port) + ConRestoreViewRect(); + /* Console has the capability to parse so pass the raw buffer to console directly */ - ConRestoreViewRect(); /* Restore the visible window, otherwise WriteConsoleW() gets messy */ - wchar_t* t = utf8_to_utf16(buf); - if (t) { - WriteConsoleW(handle, t, (DWORD)wcslen(t), 0, 0); - free(t); - } - - ConSaveViewRect(); + WriteFile(handle, buf, len, 0, 0); + + if (track_view_port) + ConSaveViewRect(); + return; } diff --git a/contrib/win32/win32compat/tnnet.h b/contrib/win32/win32compat/tnnet.h index c3f5c312d..610236ffd 100644 --- a/contrib/win32/win32compat/tnnet.h +++ b/contrib/win32/win32compat/tnnet.h @@ -35,6 +35,6 @@ #ifndef __TNNET_H #define __TNNET_H -void processBuffer(HANDLE handle, char *buf, size_t len, unsigned char **respbuf, size_t *resplen); +void processBuffer(HANDLE handle, char *buf, DWORD len, unsigned char **respbuf, size_t *resplen); #endif \ No newline at end of file diff --git a/contrib/win32/win32compat/w32api_proxies.c b/contrib/win32/win32compat/w32api_proxies.c index b20d988c0..20fa9fa42 100644 --- a/contrib/win32/win32compat/w32api_proxies.c +++ b/contrib/win32/win32compat/w32api_proxies.c @@ -51,8 +51,8 @@ system32_dir() static HMODULE load_module(wchar_t* name) { - HMODULE hm; - + HMODULE hm = NULL; + /*system uses a standard search strategy to find the module */ if ((hm = LoadLibraryW(name)) == NULL) debug3("unable to load module %ls at run time, error: %d", name, GetLastError()); @@ -82,6 +82,17 @@ load_advapi32() return s_hm_advapi32; } +static HMODULE +load_api_security_lsapolicy() +{ + static HMODULE s_hm_api_security_lsapolicy = NULL; + + if (!s_hm_api_security_lsapolicy) + s_hm_api_security_lsapolicy = load_module(L"api-ms-win-security-lsapolicy-l1-1-0.dll"); + + return s_hm_api_security_lsapolicy; +} + static HMODULE load_secur32() { @@ -97,7 +108,7 @@ static HMODULE load_ntdll() { static HMODULE s_hm_ntdll = NULL; - + if (!s_hm_ntdll) s_hm_ntdll = load_module(L"ntdll.dll"); @@ -106,6 +117,9 @@ load_ntdll() FARPROC get_proc_address(HMODULE hm, char* fn) { + if (hm == NULL) { + debug3("GetProcAddress of %s failed with error %d.", fn, GetLastError()); + } FARPROC ret = GetProcAddress(hm, fn); if (!ret) debug3("GetProcAddress of %s failed with error %d.", fn, GetLastError()); @@ -119,6 +133,7 @@ pLogonUserExExW(wchar_t *user_name, wchar_t *domain, wchar_t *password, DWORD lo PVOID *profile_buffer, LPDWORD profile_length, PQUOTA_LIMITS quota_limits) { HMODULE hm = NULL; + typedef BOOL(WINAPI *LogonUserExExWType)(wchar_t*, wchar_t*, wchar_t*, DWORD, DWORD, PTOKEN_GROUPS, PHANDLE, PSID, PVOID, LPDWORD, PQUOTA_LIMITS); static LogonUserExExWType s_pLogonUserExExW = NULL; @@ -153,15 +168,84 @@ BOOLEAN pTranslateNameW(LPCWSTR name, if ((s_pTranslateNameW = (TranslateNameWType)get_proc_address(hm, "TranslateNameW")) == NULL) return FALSE; - } - + } return s_pTranslateNameW(name, account_format, desired_name_format, translated_name, psize); } -ULONG pRtlNtStatusToDosError(NTSTATUS status) +NTSTATUS pLsaOpenPolicy(PLSA_UNICODE_STRING system_name, + PLSA_OBJECT_ATTRIBUTES attrib, + ACCESS_MASK access, + PLSA_HANDLE handle) { HMODULE hm = NULL; - typedef ULONG(NTAPI *RtlNtStatusToDosErrorType)(NTSTATUS); + typedef NTSTATUS(NTAPI *LsaOpenPolicyType)(PLSA_UNICODE_STRING, PLSA_OBJECT_ATTRIBUTES, ACCESS_MASK, PLSA_HANDLE); + static LsaOpenPolicyType s_pLsaOpenPolicy = NULL; + if (!s_pLsaOpenPolicy) { + if ((hm = load_api_security_lsapolicy()) == NULL && + ((hm = load_advapi32()) == NULL)) + return STATUS_ASSERTION_FAILURE; + if ((s_pLsaOpenPolicy = (LsaOpenPolicyType)get_proc_address(hm, "LsaOpenPolicy")) == NULL) + return STATUS_ASSERTION_FAILURE; + } + return s_pLsaOpenPolicy(system_name, attrib, access, handle); +} +NTSTATUS pLsaFreeMemory(PVOID buffer) +{ + HMODULE hm = NULL; + typedef NTSTATUS(NTAPI *LsaFreeMemoryType)(PVOID); + static LsaFreeMemoryType s_pLsaFreeMemory = NULL; + if (!s_pLsaFreeMemory) { + if ((hm = load_api_security_lsapolicy()) == NULL && + ((hm = load_advapi32()) == NULL)) + return STATUS_ASSERTION_FAILURE; + if ((s_pLsaFreeMemory = (LsaFreeMemoryType)get_proc_address(hm, "LsaFreeMemory")) == NULL) + return STATUS_ASSERTION_FAILURE; + } + return s_pLsaFreeMemory(buffer); +} +NTSTATUS pLsaAddAccountRights(LSA_HANDLE lsa_h, + PSID psid, + PLSA_UNICODE_STRING rights, + ULONG num_rights) +{ + HMODULE hm = NULL; + typedef NTSTATUS(NTAPI *LsaAddAccountRightsType)(LSA_HANDLE, PSID, PLSA_UNICODE_STRING, ULONG); + static LsaAddAccountRightsType s_pLsaAddAccountRights = NULL; + if (!s_pLsaAddAccountRights) { + if ((hm = load_api_security_lsapolicy()) == NULL && + ((hm = load_advapi32()) == NULL)) + return STATUS_ASSERTION_FAILURE; + if ((s_pLsaAddAccountRights = (LsaAddAccountRightsType)get_proc_address(hm, "LsaAddAccountRights")) == NULL) + return STATUS_ASSERTION_FAILURE; + } + + return s_pLsaAddAccountRights(lsa_h, psid, rights, num_rights); +} + +NTSTATUS pLsaRemoveAccountRights(LSA_HANDLE lsa_h, + PSID psid, + BOOLEAN all_rights, + PLSA_UNICODE_STRING rights, + ULONG num_rights) +{ + HMODULE hm = NULL; + typedef NTSTATUS(NTAPI *LsaRemoveAccountRightsType)(LSA_HANDLE, PSID, BOOLEAN, PLSA_UNICODE_STRING, ULONG); + static LsaRemoveAccountRightsType s_pLsaRemoveAccountRights = NULL; + if (!s_pLsaRemoveAccountRights) { + if ((hm = load_api_security_lsapolicy()) == NULL && + ((hm = load_advapi32()) == NULL)) + return STATUS_ASSERTION_FAILURE; + if ((s_pLsaRemoveAccountRights = (LsaRemoveAccountRightsType)get_proc_address(hm, "LsaRemoveAccountRights")) == NULL) + return STATUS_ASSERTION_FAILURE; + } + + return s_pLsaRemoveAccountRights(lsa_h, psid, all_rights, rights, num_rights); +} + +ULONG pRtlNtStatusToDosError(NTSTATUS status) +{ + HMODULE hm = NULL; + typedef ULONG(NTAPI *RtlNtStatusToDosErrorType)(NTSTATUS); static RtlNtStatusToDosErrorType s_pRtlNtStatusToDosError = NULL; if (!s_pRtlNtStatusToDosError) { @@ -170,6 +254,24 @@ ULONG pRtlNtStatusToDosError(NTSTATUS status) if ((s_pRtlNtStatusToDosError = (RtlNtStatusToDosErrorType)get_proc_address(hm, "RtlNtStatusToDosError")) == NULL) return STATUS_ASSERTION_FAILURE; - } + } return pRtlNtStatusToDosError(status); } + +NTSTATUS pLsaClose(LSA_HANDLE lsa_h) +{ + HMODULE hm = NULL; + typedef NTSTATUS(NTAPI *LsaCloseType)(LSA_HANDLE); + static LsaCloseType s_pLsaClose = NULL; + + if (!s_pLsaClose) { + if ((hm = load_api_security_lsapolicy()) == NULL && + ((hm = load_advapi32()) == NULL)) + return STATUS_ASSERTION_FAILURE; + + if ((s_pLsaClose = (LsaCloseType)get_proc_address(hm, "LsaClose")) == NULL) + return STATUS_ASSERTION_FAILURE; + } + + return s_pLsaClose(lsa_h); +} diff --git a/contrib/win32/win32compat/w32api_proxies.h b/contrib/win32/win32compat/w32api_proxies.h index 11073435c..a47d83b9c 100644 --- a/contrib/win32/win32compat/w32api_proxies.h +++ b/contrib/win32/win32compat/w32api_proxies.h @@ -15,8 +15,10 @@ BOOL pLogonUserExExW(wchar_t *, wchar_t *, wchar_t *, DWORD, DWORD, PTOKEN_GROUPS, PHANDLE, PSID *, PVOID *, LPDWORD, PQUOTA_LIMITS); BOOLEAN pTranslateNameW(LPCWSTR, EXTENDED_NAME_FORMAT, EXTENDED_NAME_FORMAT, LPWSTR, PULONG); NTSTATUS pLsaOpenPolicy(PLSA_UNICODE_STRING, PLSA_OBJECT_ATTRIBUTES, ACCESS_MASK, PLSA_HANDLE); +NTSTATUS pLsaFreeMemory(PVOID); NTSTATUS pLsaAddAccountRights(LSA_HANDLE, PSID, PLSA_UNICODE_STRING, ULONG); ULONG pRtlNtStatusToDosError(NTSTATUS); - +NTSTATUS pLsaClose(LSA_HANDLE); +NTSTATUS pLsaRemoveAccountRights(LSA_HANDLE, PSID, BOOLEAN, PLSA_UNICODE_STRING, ULONG); diff --git a/contrib/win32/win32compat/w32fd.c b/contrib/win32/win32compat/w32fd.c index 863061ff5..dc612a71c 100644 --- a/contrib/win32/win32compat/w32fd.c +++ b/contrib/win32/win32compat/w32fd.c @@ -33,6 +33,7 @@ #include "inc\sys\select.h" #include "inc\sys\uio.h" #include "inc\sys\types.h" +#include "inc\sys\stat.h" #include "inc\unistd.h" #include "inc\fcntl.h" #include "inc\sys\un.h" @@ -73,6 +74,11 @@ void fd_decode_state(char*); /* __progname */ char* __progname = ""; +/* __progdir */ +char* __progdir = ""; +wchar_t* __wprogdir = L""; + + /* initializes mapping table*/ static int fd_table_initialize() @@ -176,41 +182,42 @@ fd_table_clear(int index) FD_CLR(index, &(fd_table.occupied)); } -/* TODO - consolidate w32_programdir logic in here */ -static int +void init_prog_paths() { wchar_t* wpgmptr; - char* pgmptr; + static int processed = 0; - if (_get_wpgmptr(&wpgmptr) != 0) { - errno = EOTHER; - return -1; - } + if (processed) + return; - if ((pgmptr = utf16_to_utf8(wpgmptr)) == NULL) { - errno = ENOMEM; - return -1; - } + if (_get_wpgmptr(&wpgmptr) != 0) + fatal("unable to retrieve wpgmptr"); - __progname = strrchr(pgmptr, '\\') + 1; - *(__progname - 1) = '\0'; + if ((__wprogdir = _wcsdup(wpgmptr)) == NULL || + (__progdir = utf16_to_utf8(__wprogdir)) == NULL) + fatal("out of memory"); + + __progname = strrchr(__progdir, '\\') + 1; + /* TODO: retain trailing \ at the end of progdir* variants ? */ + *(strrchr(__progdir, '\\')) = '\0'; + *(wcsrchr(__wprogdir, L'\\')) = L'\0'; /* strip .exe off __progname */ *(__progname + strlen(__progname) - 4) = '\0'; - return 0; + processed = 1; } void w32posix_initialize() { + init_prog_paths(); if ((fd_table_initialize() != 0) || (socketio_initialize() != 0)) DebugBreak(); main_thread = OpenThread(THREAD_SET_CONTEXT | SYNCHRONIZE, FALSE, GetCurrentThreadId()); if (main_thread == NULL || - sw_initialize() != 0 || - init_prog_paths() != 0 ) { + sw_initialize() != 0 ) { DebugBreak(); fatal("failed to initialize w32posix wrapper"); } @@ -962,6 +969,28 @@ w32_ftruncate(int fd, off_t length) return 0; } +int w32_fchmod(int fd, mode_t mode) +{ + wchar_t *file_path; + char *file_path_utf8 = NULL; + int ret = -1; + CHECK_FD(fd); + + file_path = get_final_path_by_handle(fd_table.w32_ios[fd]->handle); + if (!file_path) + goto cleanup; + + if ((file_path_utf8 = utf16_to_utf8(file_path)) == NULL) + goto cleanup; + + ret = w32_chmod(file_path_utf8, mode); +cleanup: + if (file_path_utf8) + free(file_path_utf8); + + return ret; +} + int w32_fsync(int fd) { @@ -1012,7 +1041,7 @@ spawn_child_internal(char* cmd, char *const argv[], HANDLE in, HANDLE out, HANDL /* compute total cmdline len*/ if (add_module_path) - cmdline_len += (DWORD)strlen(w32_programdir()) + 1 + (DWORD)strlen(cmd) + 1 + 2; + cmdline_len += (DWORD)strlen(__progdir) + 1 + (DWORD)strlen(cmd) + 1 + 2; else cmdline_len += (DWORD)strlen(cmd) + 1 + 2; @@ -1032,8 +1061,8 @@ spawn_child_internal(char* cmd, char *const argv[], HANDLE in, HANDLE out, HANDL if (argv && argv[0]) *t++ = '\"'; if (add_module_path) { - memcpy(t, w32_programdir(), strlen(w32_programdir())); - t += strlen(w32_programdir()); + memcpy(t, __progdir, strlen(__progdir)); + t += strlen(__progdir); *t++ = '\\'; } diff --git a/contrib/win32/win32compat/win32_pty.c b/contrib/win32/win32compat/win32_pty.c new file mode 100644 index 000000000..3d725065c --- /dev/null +++ b/contrib/win32/win32compat/win32_pty.c @@ -0,0 +1,120 @@ +/* +* Author: Balu G +* +* This file contains the conpty related functions. +* +* 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 +#include +#include +#include +#include "Debug.h" +#include "inc\fcntl.h" +#include "misc_internal.h" + +// Return Value: 0 for success, -1 for failure +int +CreateConPty(const wchar_t *cmdline, + const unsigned short width, + const unsigned short height, + HANDLE const hInput, + HANDLE const hOutput, + HANDLE const tty_sighandle, + PROCESS_INFORMATION* const piPty) +{ + wchar_t system32_path[PATH_MAX] = { 0, }; + + SetHandleInformation(tty_sighandle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); + + wchar_t conhostCmdline[8191] = { 0, }; // msdn + wchar_t *cmd_fmt = L"%ls\\conhost.exe --headless --width %d --height %d --signal 0x%x -- %ls"; + + if (!GetSystemDirectoryW(system32_path, PATH_MAX)) + fatal("unable to retrieve system32 path"); + + _snwprintf_s(conhostCmdline, + _countof(conhostCmdline), + _countof(conhostCmdline), + cmd_fmt, + system32_path, + width, + height, + tty_sighandle, + cmdline); + + STARTUPINFOW si; + memset(&si, 0, sizeof(STARTUPINFOW)); + si.cb = sizeof(STARTUPINFOW); + si.hStdInput = hInput; + si.hStdOutput = hOutput; + si.hStdError = hOutput; + si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESIZE | STARTF_USECOUNTCHARS; + + debug3("pty commandline: %ls", conhostCmdline); + + if (0 == CreateProcessW(NULL, conhostCmdline, NULL, NULL, TRUE, 0, NULL, NULL, &si, piPty)) { + debug("%s - failed to execute %ls, error:%d", __func__, conhostCmdline, GetLastError()); + errno = EOTHER; + return -1; + } + + return 0; +} + +int is_conpty_supported() +{ + /* TODO - enable this once conpty changes are validated */ + return 0; +} + +int exec_command_with_pty(wchar_t* cmd, STARTUPINFOW* si, PROCESS_INFORMATION* pi, int ttyfd) +{ + HANDLE ttyh = (HANDLE)w32_fd_to_handle(ttyfd); + wchar_t pty_cmdline[MAX_CMD_LEN] = { 0, }; + int ret = -1; + + if (is_conpty_supported()) + return CreateConPty(cmd, (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); + /* + * 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; + debug3("pty commandline: %ls", pty_cmdline); + + if (!CreateProcessW(NULL, pty_cmdline, NULL, NULL, TRUE, 0, NULL, NULL, si, pi)) { + debug("%s - failed to execute %ls, error:%d", __func__, pty_cmdline, GetLastError()); + errno = EOTHER; + goto done; + } + + ret = 0; + +done: + return ret; +} \ No newline at end of file diff --git a/contrib/win32/win32compat/win32_sshpty.c b/contrib/win32/win32compat/win32_sshpty.c index f4f4e459a..8e1e6c461 100644 --- a/contrib/win32/win32compat/win32_sshpty.c +++ b/contrib/win32/win32compat/win32_sshpty.c @@ -6,23 +6,34 @@ #include #include "..\..\..\sshpty.h" - +#include "inc\unistd.h" +#include "misc_internal.h" /* * Windows versions of pty_*. Some of them are NO-OPs and should go * away when pty logic is refactored and abstracted out * */ + + /* + * allocates a control channel for Windows PTY + * ptyfd can be used to deliver Window size change events + */ int pty_allocate(int *ptyfd, int *ttyfd, char *namebuf, size_t namebuflen) { - /* - * Simple console screen implementation in Win32 to give a - * Unix like pty for interactive sessions - */ + int p[2]; *ttyfd = 0; *ptyfd = 0; - strlcpy(namebuf, "console", namebuflen); + if (w32_pipe(p) < 0) + return 0; + + /* enable blocking mode io*/ + unset_nonblock(p[0]); + unset_nonblock(p[1]); + *ttyfd = p[0]; + *ptyfd = p[1]; + strlcpy(namebuf, "windows-pty", namebuflen); return 1; } @@ -38,8 +49,15 @@ pty_make_controlling_tty(int *ttyfd, const char *tty) { void pty_change_window_size(int ptyfd, u_int row, u_int col, - u_int xpixel, u_int ypixel) { - /* TODO - Need to implement*/ + u_int xpixel, u_int ypixel) +{ + unsigned short signalPacket[3]; + signalPacket[0] = PTY_SIGNAL_RESIZE_WINDOW; + signalPacket[1] = col; + signalPacket[2] = row; + // TODO - xpixel, ypixel + + w32_write(ptyfd, signalPacket, sizeof(signalPacket)); } diff --git a/contrib/win32/win32compat/win32_usertoken_utils.c b/contrib/win32/win32compat/win32_usertoken_utils.c index 7598cb9d0..f9838efd5 100644 --- a/contrib/win32/win32compat/win32_usertoken_utils.c +++ b/contrib/win32/win32compat/win32_usertoken_utils.c @@ -135,6 +135,7 @@ generate_s4u_user_token(wchar_t* user_cpn, int impersonation) { /* lookup the user principal name for the account */ WCHAR domain_upn[MAX_UPN_LEN + 1]; + if (lookup_principal_name(user_cpn, domain_upn) != 0) { /* failure - fallback to NetBiosDomain\SamAccountName */ wcscpy_s(domain_upn, ARRAYSIZE(domain_upn), user_cpn); @@ -488,7 +489,7 @@ add_sid_mapping_to_lsa(PUNICODE_STRING domain_name, } if (p_output) { - status = LsaFreeMemory(p_output); + status = pLsaFreeMemory(p_output); if (status != STATUS_SUCCESS) debug3("LsaFreeMemory failed with ntstatus: %d", status); } @@ -517,7 +518,7 @@ int remove_virtual_account_lsa_mapping(PUNICODE_STRING domain_name, ret = -1; if (p_output) { - status = LsaFreeMemory(p_output); + status = pLsaFreeMemory(p_output); if (status != STATUS_SUCCESS) debug3("LsaFreeMemory failed with ntstatus: %d", status); } @@ -623,7 +624,7 @@ HANDLE generate_sshd_virtual_token() /* assign service logon privilege to virtual account */ ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes)); - if ((lsa_ret = LsaOpenPolicy(NULL, &ObjectAttributes, + if ((lsa_ret = pLsaOpenPolicy(NULL, &ObjectAttributes, POLICY_CREATE_ACCOUNT | POLICY_LOOKUP_NAMES, &lsa_policy)) != STATUS_SUCCESS) { error("%s: unable to open policy handle, error: %d", @@ -632,7 +633,7 @@ HANDLE generate_sshd_virtual_token() } /* alter security to allow policy to account to logon as a service */ - if ((lsa_add_ret = LsaAddAccountRights(lsa_policy, sid_user, &svcLogonRight, 1)) != STATUS_SUCCESS) { + if ((lsa_add_ret = pLsaAddAccountRights(lsa_policy, sid_user, &svcLogonRight, 1)) != STATUS_SUCCESS) { error("%s: unable to assign SE_SERVICE_LOGON_NAME privilege, error: %d", __FUNCTION__, (ULONG)pRtlNtStatusToDosError(lsa_add_ret)); goto cleanup; @@ -656,7 +657,7 @@ cleanup: /* attempt to remove virtual account permissions if previous add succeeded */ if (lsa_add_ret == STATUS_SUCCESS) - if ((lsa_ret = LsaRemoveAccountRights(lsa_policy, sid_user, FALSE, &svcLogonRight, 1)) != STATUS_SUCCESS) + if ((lsa_ret = pLsaRemoveAccountRights(lsa_policy, sid_user, FALSE, &svcLogonRight, 1)) != STATUS_SUCCESS) debug("%s: unable to remove SE_SERVICE_LOGON_NAME privilege, error: %d", __FUNCTION__, pRtlNtStatusToDosError(lsa_ret)); if (sid_domain) @@ -666,7 +667,7 @@ cleanup: if (sid_group) FreeSid(sid_group); if (lsa_policy) - LsaClose(lsa_policy); + pLsaClose(lsa_policy); return va_token_restricted; } @@ -709,6 +710,24 @@ get_custom_lsa_package() return s_lsa_auth_pkg; } +/* + * Not thread safe + * returned value is pointer from static buffer + * dont free() + */ +wchar_t* get_final_path_by_handle(HANDLE h) +{ + static wchar_t path_buf[PATH_MAX]; + + if (GetFinalPathNameByHandleW(h, path_buf, PATH_MAX, 0) == 0) { + errno = EOTHER; + debug3("failed to get final path of file with handle:%d error:%d", h, GetLastError()); + return NULL; + } + + return (path_buf + 4); +} + /* using the netbiosname\samaccountname as an input, lookup the upn for the user. * if no explicit upn is defined, implicit upn is returned (samaccountname@fqdn) */ int lookup_principal_name(const wchar_t * sam_account_name, wchar_t * user_principal_name) diff --git a/contrib/win32/win32compat/wmain_sshd.c b/contrib/win32/win32compat/wmain_sshd.c index 6177a495f..1e40a8eda 100644 --- a/contrib/win32/win32compat/wmain_sshd.c +++ b/contrib/win32/win32compat/wmain_sshd.c @@ -189,7 +189,7 @@ create_prgdata_ssh_folder() wcscat_s(sshd_config_path, _countof(sshd_config_path), L"\\sshd_config"); if (GetFileAttributesW(sshd_config_path) == INVALID_FILE_ATTRIBUTES) { wchar_t sshd_config_default_path[PATH_MAX] = { 0, }; - swprintf_s(sshd_config_default_path, PATH_MAX, L"%S\\%s", w32_programdir(), L"sshd_config_default"); + swprintf_s(sshd_config_default_path, PATH_MAX, L"%S\\%s", __progdir, L"sshd_config_default"); if (CopyFileW(sshd_config_default_path, sshd_config_path, TRUE) == 0) { printf("Failed to copy %s to %s, error:%d", sshd_config_default_path, sshd_config_path, GetLastError()); @@ -264,11 +264,9 @@ int wmain(int argc, wchar_t **wargv) { argc_original = argc; wargv_original = wargv; + init_prog_paths(); /* change current directory to sshd.exe root */ - if ( (path_utf16 = utf8_to_utf16(w32_programdir())) == NULL) - return -1; - _wchdir(path_utf16); - free(path_utf16); + _wchdir(__wprogdir); if (!StartServiceCtrlDispatcherW(dispatch_table)) { if (GetLastError() == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) diff --git a/hostfile.c b/hostfile.c index 6a485d1f7..12f174ff9 100644 --- a/hostfile.c +++ b/hostfile.c @@ -524,11 +524,6 @@ int hostfile_replace_entries(const char *filename, const char *host, const char *ip, struct sshkey **keys, size_t nkeys, int store_hash, int quiet, int hash_alg) { -#ifdef WINDOWS - error("replacing host file entries is not supported in Windows yet"); - errno = ENOTSUP; - return -1; -#else /* !WINDOWS */ int r, fd, oerrno = 0; int loglevel = quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_VERBOSE; struct host_delete_ctx ctx; @@ -643,7 +638,6 @@ hostfile_replace_entries(const char *filename, const char *host, const char *ip, if (r == SSH_ERR_SYSTEM_ERROR) errno = oerrno; return r; -#endif /* !WINDOWS */ } static int diff --git a/regress/pesterTests/KeyUtils.Tests.ps1 b/regress/pesterTests/KeyUtils.Tests.ps1 index da680851d..9de1ff851 100644 --- a/regress/pesterTests/KeyUtils.Tests.ps1 +++ b/regress/pesterTests/KeyUtils.Tests.ps1 @@ -295,6 +295,30 @@ Describe "E2E scenarios for ssh key management" -Tags "CI" { } } + Context "$tC ssh-keygen known_hosts operations" { + + BeforeAll {$tI=1} + AfterAll{$tC++} + + It "$tC.$tI - list and delete host key thumbprints" { + $kh = Join-Path $testDir "$tC.$tI.known_hosts" + $entry = "[localhost]:47002 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMtJMxwn+iJU0X4+EC7PSj/cfcMbdP6ahhodtXx+6RHv sshtest_hostkey_ed25519" + $entry | Set-Content $kh + $o = ssh-keygen -F [localhost]:47002 -f $kh + $o.Count | Should Be 2 + $o[1] | Should Be $entry + + $o = ssh-keygen -H -F [localhost]:47002 -f $kh + $o.StartsWith("|1|") | Should Be $true + + $o = ssh-keygen -R [localhost]:47002 -f $kh + $o.count | Should Be 3 + $o[0] | Should Be "# Host [localhost]:47002 found: line 1" + (dir $kh).Length | Should Be 0 + } + + } + Context "$tC-ssh-add key files with different file perms" { BeforeAll { $keyFileName = "sshadd_userPermTestkey_ed25519" diff --git a/regress/pesterTests/SSH.Tests.ps1 b/regress/pesterTests/SSH.Tests.ps1 index 81f764326..b1d2aaa64 100644 --- a/regress/pesterTests/SSH.Tests.ps1 +++ b/regress/pesterTests/SSH.Tests.ps1 @@ -81,14 +81,16 @@ Describe "E2E scenarios for ssh client" -Tags "CI" { param ( [string] $default_shell_path, - [string] $default_shell_cmd_option_val + [string] $default_shell_cmd_option_val = $null ) if (!(Test-Path $dfltShellRegPath)) { New-Item -Path $dfltShellRegPath -Force | Out-Null } New-ItemProperty -Path $dfltShellRegPath -Name $dfltShellRegKeyName -Value $default_shell_path -PropertyType String -Force - New-ItemProperty -Path $dfltShellRegPath -Name $dfltShellCmdOptionRegKeyName -Value $default_shell_cmd_option_val -PropertyType String -Force + if ($default_shell_cmd_option_val -ne $null) { + New-ItemProperty -Path $dfltShellRegPath -Name $dfltShellCmdOptionRegKeyName -Value $default_shell_cmd_option_val -PropertyType String -Force + } } } @@ -148,6 +150,12 @@ Describe "E2E scenarios for ssh client" -Tags "CI" { $o | Should Be "1234" } + It "$tC.$tI - 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" + } + It "$tC.$tI - stdin from PS object" -skip:$skip { # execute this script that dumps the length of input data, on the remote end $str = "begin {} process { Write-Output `$input.Length} end { }" @@ -211,6 +219,14 @@ Describe "E2E scenarios for ssh client" -Tags "CI" { $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\" + $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" + } } Context "$tC - cmdline parameters" { @@ -271,105 +287,15 @@ Describe "E2E scenarios for ssh client" -Tags "CI" { $o | Should Be "1234" $logFile | Should Contain "[::1]" } - } - - - <#Context "Key is not secured in ssh-agent on server" { - BeforeAll { - $identifyFile = $client.clientPrivateKeyPaths[0] - Remove-Item -Path $filePath -Force -ea silentlycontinue - } - - AfterEach { - Remove-Item -Path $filePath -Force -ea silentlycontinue - } - - It '' -TestCases:$testData1 { - param([string]$Title, $LogonStr, $Options, $SkipVerification = $false) - - $str = $ExecutionContext.InvokeCommand.ExpandString(".\ssh $($Options) $($LogonStr) hostname > $filePath") - $client.RunCmd($str) - #validate file content. - Get-Content $filePath | Should be $server.MachineName + It "$tC.$tI - auto populate known hosts" { + + $kh = Join-Path $testDir "$tC.$tI.known_hosts" + $nul | Set-Content $kh + # doing via cmd to intercept and drain stderr output + iex "cmd /c `"ssh -o UserKnownHostsFile=`"$kh`" -o StrictHostKeyChecking=no test_target hostname 2>&1`"" + (Get-Content $kh).Count | Should Be 1 } } - Context "Key is secured in ssh-agent" { - BeforeAll { - $server.SecureHostKeys($server.PrivateHostKeyPaths) - $identifyFile = $client.clientPrivateKeyPaths[0] - Remove-Item -Path $filePath -Force -ea silentlycontinue - } - - AfterAll { - $Server.CleanupHostKeys() - } - - AfterEach { - Remove-Item -Path $filePath -Force -ea silentlycontinue - } - - It '<Title>' -TestCases:$testData1 { - param([string]$Title, $LogonStr, $Options, $SkipVerification = $false) - - $str = $ExecutionContext.InvokeCommand.ExpandString(".\ssh $Options $LogonStr hostname > $filePath") - $client.RunCmd($str) - #validate file content. - Get-Content $filePath | Should be $server.MachineName - } - } - - Context "Single signon on client and keys secured in ssh-agent on server" { - BeforeAll { - $Server.SecureHostKeys($server.PrivateHostKeyPaths) - $identifyFile = $client.clientPrivateKeyPaths[0] - #setup single signon - .\ssh-add.exe $identifyFile - Remove-Item -Path $filePath -Force -ea silentlycontinue - } - - AfterAll { - $Server.CleanupHostKeys() - - #cleanup single signon - .\ssh-add.exe -D - } - - AfterEach { - Remove-Item -Path $filePath -Force -ea silentlycontinue - } - - It '<Title>' -TestCases:$testData { - param([string]$Title, $LogonStr, $Options) - - $str = ".\ssh $($Options) $($LogonStr) hostname > $filePath" - $client.RunCmd($str) - #validate file content. - Get-Content $filePath | Should be $server.MachineName - } - } - Context "password authentication" { - BeforeAll { - $client.AddPasswordSetting($server.localAdminPassword) - Remove-Item -Path $filePath -Force -ea silentlycontinue - } - - AfterAll { - $client.CleanupPasswordSetting() - } - - AfterEach { - Remove-Item -Path $filePath -Force -ea silentlycontinue - } - - It '<Title>' -TestCases:$testData { - param([string]$Title, $LogonStr, $Options) - - $str = ".\ssh $($Options) $($LogonStr) hostname > $filePath" - $client.RunCmd($str) - #validate file content. - Get-Content $filePath | Should be $server.MachineName - } - }#> } diff --git a/regress/pesterTests/ShellHost.Tests.ps1 b/regress/pesterTests/ShellHost.Tests.ps1 new file mode 100644 index 000000000..a85edf22f --- /dev/null +++ b/regress/pesterTests/ShellHost.Tests.ps1 @@ -0,0 +1,42 @@ +$tC = 1 +$tI = 0 +$suite = "shellhost" + +Describe "E2E scenarios for ssh-shellhost" -Tags "CI" { + BeforeAll { + } + + BeforeEach { + } + + AfterEach {$tI++;} + + Context "$tC - shellhost commandline scenarios" { + BeforeAll {$tI=1} + AfterAll{$tC++} + + It "$tC.$tI - exit code tests" -skip:$skip { + foreach ($i in (0,1,4,5,44)) { + ssh-shellhost -c cmd /c exit $i + $LASTEXITCODE | Should Be $i + } + } + + It "$tC.$tI - various quote tests" -skip:$skip { + $o = ssh-shellhost -c cmd /c echo hello + $o | Should Be "hello" + $o = ssh-shellhost -c `"cmd /c echo hello`" + $o | Should Be "hello" + $o = ssh-shellhost -c cmd /c echo `"hello`" + $o | Should Be "`"hello`"" + $o = ssh-shellhost -c `"cmd /c echo `"hello`"`" + $o | Should Be "`"hello`"" + $o = ssh-shellhost -c `"cmd /c echo `"hello`" + $o | Should Be "`"hello" + $o = ssh-shellhost -c `"`"cmd`" /c echo `"hello`"`" + $o | Should Be "`"hello`"" + + } + + } +} diff --git a/regress/unittests/win32compat/miscellaneous_tests.c b/regress/unittests/win32compat/miscellaneous_tests.c index f82ad5d88..7dd438253 100644 --- a/regress/unittests/win32compat/miscellaneous_tests.c +++ b/regress/unittests/win32compat/miscellaneous_tests.c @@ -64,7 +64,7 @@ test_sanitizedpath() { TEST_START("win32 program dir"); - char *win32prgdir_utf8 = w32_programdir(); + char *win32prgdir_utf8 = __progdir; ASSERT_PTR_NE(win32prgdir_utf8, NULL); ASSERT_PTR_EQ(resolved_path_utf16(NULL), NULL); @@ -314,126 +314,92 @@ test_chroot() 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); + char *progdir = __progdir, *out, buf[PATH_MAX * 2]; TEST_START("default interactive session tests"); - out = build_session_commandline("c:\\system32\\cmd.exe", NULL, NULL, 0); + out = build_session_commandline("c:\\system32\\cmd.exe", NULL, NULL); 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, "\"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); + out = build_session_commandline("c:\\system32\\cmd.exe", NULL, "internal-sftp -arg"); buf[len_pg] = '\0'; - strcat(buf, "\\sftp-server.exe\" -arg"); + 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); + out = build_session_commandline("c:\\system32\\cmd.exe", NULL, "SFTP-server.exe -arg"); buf[len_pg] = '\0'; - strcat(buf, "\\sftp-server.exe\" -arg"); + strcat(buf, "\\sftp-server.exe\" -arg\""); ASSERT_STRING_EQ(out, buf); - out = build_session_commandline("c:\\system32\\cmd.exe", NULL, "sftp-SERVER -arg", 0); + out = build_session_commandline("c:\\system32\\cmd.exe", NULL, "sftp-SERVER -arg"); buf[len_pg] = '\0'; - strcat(buf, "\\sftp-server.exe\" -arg"); + strcat(buf, "\\sftp-server.exe\" -arg\""); ASSERT_STRING_EQ(out, buf); - out = build_session_commandline("c:\\system32\\cmd.exe", NULL, "sCp -arg", 0); + out = build_session_commandline("c:\\system32\\cmd.exe", NULL, "sCp -arg"); buf[len_pg] = '\0'; - strcat(buf, "\\scp.exe\" -arg"); + 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("bash shell tests"); - out = build_session_commandline("c:\\system32\\bash.exe", NULL, "internal-sftp -arg", 0); + 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", 0); + 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", 0); + 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", 0); + 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:\\bash", "-custom", "mycommand -arg", 1); - ASSERT_STRING_EQ(out + shellhost_path_len + 1, "\"c:\\bash\" -custom \"mycommand -arg\""); - out[shellhost_path_len] = '\0'; - ASSERT_STRING_EQ(out, shellhost_path); - free(out); - out = build_session_commandline("c:\\cygwin\\bash.exe", NULL, "internal-sftp -arg", 0); + 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", 0); + 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", 0); + 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", 0); + out = build_session_commandline("c:\\cygwin\\bash", NULL, "sCp -arg"); 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"); + out = build_session_commandline("c:\\powershell.exe", NULL, "internal-sftp -arg"); + 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"); + out = build_session_commandline("c:\\powershell", NULL, "sftp-server -arg"); + 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"); + 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", 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); + 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", 0); - ASSERT_STRING_EQ(out, "\"c:\\myshell.exe\" -c sftp-server.exe -arg"); + 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", 0); - ASSERT_STRING_EQ(out, "\"c:\\myshell\" -c sftp-server.exe -arg"); + 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", 0); - ASSERT_STRING_EQ(out, "\"c:\\myshell\" -c sftp-server.exe -arg"); + 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", 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); + out = build_session_commandline("c:\\myshell", NULL, "sCp -arg"); + ASSERT_STRING_EQ(out, "\"c:\\myshell\" -c \"scp.exe -arg\""); free(out); TEST_DONE(); } diff --git a/session.c b/session.c index 14fa16214..b608dcb84 100644 --- a/session.c +++ b/session.c @@ -537,7 +537,7 @@ cleanup: } int register_child(void* child, unsigned long pid); -char* build_session_commandline(const char *, const char *, const char *, int); +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; @@ -582,7 +582,7 @@ int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) { pty = 0; } - exec_command = build_session_commandline(s->pw->pw_shell, shell_command_option, command, pty); + exec_command = build_session_commandline(s->pw->pw_shell, shell_command_option, command); if (exec_command == NULL) goto cleanup; @@ -606,11 +606,18 @@ int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) { si.hStdError = (HANDLE)w32_fd_to_handle(pipeerr[1]); si.lpDesktop = NULL; - debug("Executing command: %s", exec_command); if ((exec_command_w = utf8_to_utf16(exec_command)) == NULL) goto cleanup; - if (!CreateProcessW(NULL, exec_command_w, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { + 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; @@ -641,12 +648,6 @@ int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) { register_child(pi.hProcess, pi.dwProcessId); } - /* - * Set interactive/non-interactive mode. - */ - packet_set_interactive(s->display != NULL, options.ip_qos_interactive, - options.ip_qos_bulk); - /* Close the child sides of the socket pairs. */ close(pipein[0]); close(pipeout[1]); @@ -656,10 +657,17 @@ int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) { * Enter the interactive session. Note: server_loop must be able to * handle the case that fdin and fdout are the same. */ - if (s->ttyfd == -1) + 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); - else - session_set_fds(ssh, s, pipein[1], pipeout[0], pipeerr[0], s->is_subsystem, 1); /* tty interactive session */ + } ret = 0; diff --git a/ssh-keygen.c b/ssh-keygen.c index b0f749dc9..e0f436219 100644 --- a/ssh-keygen.c +++ b/ssh-keygen.c @@ -1053,19 +1053,12 @@ do_gen_all_hostkeys(struct passwd *pw) pub_tmp, strerror(errno)); goto failnext; } -#ifdef WINDOWS - /* Windows POSIX adpater does not support fdopen() on open(file)*/ - close(fd); - chmod(pub_tmp, 0644); - if ((f = fopen(pub_tmp, "w")) == NULL) { - error("fopen %s failed: %s", pub_tmp, strerror(errno)); -#else /* !WINDOWS */ + (void)fchmod(fd, 0644); f = fdopen(fd, "w"); if (f == NULL) { error("fdopen %s failed: %s", pub_tmp, strerror(errno)); close(fd); -#endif /* !WINDOWS */ sshkey_free(public); first = 0; continue; @@ -1232,10 +1225,6 @@ known_hosts_find_delete(struct hostkey_foreach_line *l, void *_ctx) static void do_known_hosts(struct passwd *pw, const char *name) { -#ifdef WINDOWS - fatal("Updating known_hosts is not supported in Windows yet."); -#else /* !WINDOWS */ - char *cp, tmp[PATH_MAX], old[PATH_MAX]; int r, fd, oerrno, inplace = 0; struct known_hosts_ctx ctx; @@ -1327,7 +1316,6 @@ do_known_hosts(struct passwd *pw, const char *name) } exit (find_host && !ctx.found_key); -#endif /* !WINDOWS */ } /* @@ -1530,16 +1518,9 @@ do_change_comment(struct passwd *pw) fd = open(identity_file, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == -1) fatal("Could not save your public key in %s", identity_file); -#ifdef WINDOWS - /* Windows POSIX adpater does not support fdopen() on open(file)*/ - close(fd); - if ((f = fopen(identity_file, "w")) == NULL) - fatal("fopen %s failed: %s", identity_file, strerror(errno)); -#else /* !WINDOWS */ f = fdopen(fd, "w"); if (f == NULL) fatal("fdopen %s failed: %s", identity_file, strerror(errno)); -#endif /* !WINDOWS */ if ((r = sshkey_write(public, f)) != 0) fatal("write key failed: %s", ssh_err(r)); sshkey_free(public); @@ -1784,15 +1765,8 @@ do_ca_sign(struct passwd *pw, int argc, char **argv) if ((fd = open(out, O_WRONLY|O_CREAT|O_TRUNC, 0644)) == -1) fatal("Could not open \"%s\" for writing: %s", out, strerror(errno)); -#ifdef WINDOWS - /* Windows POSIX adpater does not support fdopen() on open(file)*/ - close(fd); - if ((f = fopen(out, "w")) == NULL) - fatal("fopen %s failed: %s", identity_file, strerror(errno)); -#else /* !WINDOWS */ if ((f = fdopen(fd, "w")) == NULL) fatal("%s: fdopen: %s", __func__, strerror(errno)); -#endif /* !WINDOWS */ if ((r = sshkey_write(public, f)) != 0) fatal("Could not write certified key to %s: %s", out, ssh_err(r)); @@ -2851,15 +2825,8 @@ passphrase_again: if ((fd = open(identity_file, O_WRONLY|O_CREAT|O_TRUNC, 0644)) == -1) fatal("Unable to save public key to %s: %s", identity_file, strerror(errno)); -#ifdef WINDOWS - /* Windows POSIX adpater does not support fdopen() on open(file)*/ - close(fd); - if ((f = fopen(identity_file, "w")) == NULL) - fatal("fopen %s failed: %s", identity_file, strerror(errno)); -#else /* !WINDOWS */ if ((f = fdopen(fd, "w")) == NULL) fatal("fdopen %s failed: %s", identity_file, strerror(errno)); -#endif /* !WINDOWS */ if ((r = sshkey_write(public, f)) != 0) error("write key failed: %s", ssh_err(r)); fprintf(f, " %s\n", comment); diff --git a/sshd.c b/sshd.c index 9d5b3235a..c042b6d07 100644 --- a/sshd.c +++ b/sshd.c @@ -786,6 +786,9 @@ privsep_preauth(Authctxt *authctxt) pmonitor->m_recvfd = PRIVSEP_MONITOR_FD; pmonitor->m_log_sendfd = PRIVSEP_LOG_FD; + + fcntl(pmonitor->m_recvfd, F_SETFD, FD_CLOEXEC); + fcntl(pmonitor->m_log_sendfd, F_SETFD, FD_CLOEXEC); /* Arrange for logging to be sent to the monitor */ set_log_handler(mm_log_handler, pmonitor); @@ -948,6 +951,7 @@ privsep_postauth(Authctxt *authctxt) close(pmonitor->m_recvfd); pmonitor->m_recvfd = PRIVSEP_MONITOR_FD; + fcntl(pmonitor->m_recvfd, F_SETFD, FD_CLOEXEC); monitor_recv_keystate(pmonitor); do_setusercontext(authctxt->pw);