Refactor session logic (default shell, non PTY, child spawning) (#312)

Changes include:
- Removing sid from pwd structure to comply with Unix structure
- Integrating default shell logic within pwd
- pwd placeholder to allow logins using usernames not associated with Windows account (possible via custom LSA authentication)
- Moving all nonPTY logic from shellhost to session.c. 
- ssh-shellhost is now exclusively for implementing PTY
- Spawning all session processes from within a shell
- Validation checks in safely_chroot
This commit is contained in:
Manoj Ampalam 2018-05-22 21:49:58 -07:00 committed by GitHub
parent a479737cd5
commit 3fb0c252c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 534 additions and 648 deletions

View File

@ -366,18 +366,8 @@ createFile_flags_setup(int flags, mode_t mode, struct createFile_flags* cf_flags
return -1;
}
if ((pwd = getpwuid(0)) == NULL)
fatal("getpwuid failed.");
if ((sid_utf16 = utf8_to_utf16(pwd->pw_sid)) == NULL) {
debug3("Failed to get utf16 of the sid string");
errno = ENOMEM;
goto cleanup;
}
if (ConvertStringSidToSid(pwd->pw_sid, &owner_sid) == FALSE ||
(IsValidSid(owner_sid) == FALSE)) {
debug3("cannot retrieve SID of user %s", pwd->pw_name);
if ((owner_sid = get_user_sid(NULL)) == NULL || (!ConvertSidToStringSidW(owner_sid, &sid_utf16))) {
debug3("cannot retrieve current user's SID");
goto cleanup;
}
@ -416,9 +406,11 @@ createFile_flags_setup(int flags, mode_t mode, struct createFile_flags* cf_flags
ret = 0;
cleanup:
if (owner_sid)
LocalFree(owner_sid);
free(owner_sid);
if (sid_utf16)
free(sid_utf16);
LocalFree(sid_utf16);
return ret;
}

View File

@ -20,7 +20,6 @@ struct passwd {
gid_t pw_gid; /* numerical group ID */
char *pw_dir; /* initial working directory */
char *pw_shell; /* path to shell */
char *pw_sid; /* sid of user */
};
/*start - declarations not applicable in Windows */

View File

@ -1637,3 +1637,83 @@ chroot(const char *path)
return 0;
}
/* returns SID of user or current user if (user = NULL) */
PSID
get_user_sid(char* name)
{
HANDLE token = NULL;
TOKEN_USER* info = NULL;
DWORD info_len = 0;
PSID ret = NULL, psid;
wchar_t* name_utf16 = NULL;
if (name) {
DWORD sid_len = 0;
SID_NAME_USE n_use;
WCHAR dom[DNLEN + 1] = L"";
DWORD dom_len = DNLEN + 1;
if ((name_utf16 = utf8_to_utf16(name)) == NULL)
goto cleanup;
LookupAccountNameW(NULL, name_utf16, NULL, &sid_len, dom, &dom_len, &n_use);
if (sid_len == 0) {
errno = errno_from_Win32LastError();
goto cleanup;
}
if ((psid = malloc(sid_len)) == NULL) {
errno = ENOMEM;
goto cleanup;
}
if (!LookupAccountNameW(NULL, name_utf16, psid, &sid_len, dom, &dom_len, &n_use)) {
errno = errno_from_Win32LastError();
goto cleanup;
}
}
else {
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) == FALSE ||
GetTokenInformation(token, TokenUser, NULL, 0, &info_len) == TRUE) {
errno = EOTHER;
goto cleanup;
}
if ((info = (TOKEN_USER*)malloc(info_len)) == NULL) {
errno = ENOMEM;
goto cleanup;
}
if (GetTokenInformation(token, TokenUser, info, info_len, &info_len) == FALSE) {
errno = errno_from_Win32LastError();
goto cleanup;
}
if ((psid = malloc(GetLengthSid(info->User.Sid))) == NULL) {
errno = ENOMEM;
goto cleanup;
}
if (!CopySid(GetLengthSid(info->User.Sid), psid, info->User.Sid)) {
errno = errno_from_Win32LastError();
goto cleanup;
}
}
ret = psid;
psid = NULL;
cleanup:
if (token)
CloseHandle(token);
if (name_utf16)
free(name_utf16);
if (psid)
free(psid);
if (info)
free(info);
return ret;
}

View File

@ -52,3 +52,4 @@ 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*);
PSID get_user_sid(char*);

View File

@ -49,32 +49,73 @@
static struct passwd pw;
static char* pw_shellpath = NULL;
#define SHELL_HOST "\\ssh-shellhost.exe"
char* shell_command_option = NULL;
/* returns 0 on success, and -1 with errno set on failure */
static int
set_defaultshell()
{
HKEY reg_key = 0;
int tmp_len, ret = -1;
REGSAM mask = STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_WOW64_64KEY;
wchar_t path_buf[PATH_MAX], option_buf[32];
char *pw_shellpath_local = NULL, *command_option_local = NULL;
errno = 0;
/* if already set, return success */
if (pw_shellpath != NULL)
return 0;
path_buf[0] = L'\0';
option_buf[0] = L'\0';
tmp_len = _countof(path_buf);
if ((RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\OpenSSH", 0, mask, &reg_key) == ERROR_SUCCESS) &&
(RegQueryValueExW(reg_key, L"DefaultShell", 0, NULL, (LPBYTE)path_buf, &tmp_len) == ERROR_SUCCESS) &&
(path_buf[0] != L'\0')) {
/* fetched default shell path from registry */
tmp_len = _countof(option_buf);
if (RegQueryValueExW(reg_key, L"DefaultShellCommandOption", 0, NULL, (LPBYTE)option_buf, &tmp_len) != ERROR_SUCCESS)
option_buf[0] = L'\0';
} else {
if (!GetSystemDirectoryW(path_buf, _countof(path_buf))) {
errno = GetLastError();
goto cleanup;
}
if (wcscat_s(path_buf, _countof(path_buf), L"\\cmd.exe") != 0)
goto cleanup;
}
if ((pw_shellpath_local = utf16_to_utf8(path_buf)) == NULL)
goto cleanup;
if (option_buf[0] != L'\0')
if ((command_option_local = utf16_to_utf8(option_buf)) == NULL)
goto cleanup;
pw_shellpath = pw_shellpath_local;
pw_shellpath_local = NULL;
shell_command_option = command_option_local;
command_option_local = NULL;
ret = 0;
cleanup:
if (pw_shellpath_local)
free(pw_shellpath_local);
if (command_option_local)
free(command_option_local);
return ret;
}
int
initialize_pw()
{
errno_t r = 0;
char* program_dir = w32_programdir();
size_t program_dir_len = strlen(program_dir);
size_t shell_host_len = strlen(SHELL_HOST);
if (pw_shellpath == NULL) {
if ((pw_shellpath = malloc(program_dir_len + shell_host_len + 1)) == NULL)
fatal("initialize_pw - out of memory");
else {
char* head = pw_shellpath;
if ((r= memcpy_s(head, program_dir_len + shell_host_len + 1, w32_programdir(), program_dir_len)) != 0) {
fatal("memcpy_s failed with error: %d.", r);
}
head += program_dir_len;
if ((r = memcpy_s(head, shell_host_len + 1, SHELL_HOST, shell_host_len)) != 0) {
fatal("memcpy_s failed with error: %d.", r);
}
head += shell_host_len;
*head = '\0';
}
}
if (set_defaultshell() != 0)
return -1;
if (pw.pw_shell != pw_shellpath) {
memset(&pw, 0, sizeof(pw));
@ -87,19 +128,26 @@ initialize_pw()
return 0;
}
void
reset_pw()
static void
clean_pw()
{
initialize_pw();
if (pw.pw_name)
free(pw.pw_name);
if (pw.pw_dir)
free(pw.pw_dir);
if (pw.pw_sid)
free(pw.pw_sid);
pw.pw_name = NULL;
pw.pw_dir = NULL;
pw.pw_sid = NULL;
}
static int
reset_pw()
{
if (initialize_pw() != 0)
return -1;
clean_pw();
return 0;
}
static struct passwd*
@ -119,13 +167,13 @@ get_passwd(const wchar_t * user_utf16, PSID sid)
SID_NAME_USE account_type = 0;
errno = 0;
reset_pw();
if (reset_pw() != 0)
return NULL;
/* skip forward lookup on name if sid was passed in */
if (sid != NULL)
CopySid(sizeof(binary_sid), binary_sid, sid);
/* attempt to lookup the account; this will verify the account is valid and
/* else attempt to lookup the account; this will verify the account is valid and
* is will return its sid and the realm that owns it */
else if(LookupAccountNameW(NULL, user_utf16, binary_sid, &sid_size,
domain_name, &domain_name_size, &account_type) == 0) {
@ -136,7 +184,7 @@ get_passwd(const wchar_t * user_utf16, PSID sid)
/* convert the binary string to a string */
if (ConvertSidToStringSidW((PSID) binary_sid, &sid_string) == FALSE) {
errno = ENOENT;
errno = errno_from_Win32LastError();
goto cleanup;
}
@ -146,7 +194,7 @@ get_passwd(const wchar_t * user_utf16, PSID sid)
domain_name_size = DNLEN + 1;
if (LookupAccountSidW(NULL, binary_sid, user_name, &user_name_length,
domain_name, &domain_name_size, &account_type) == 0) {
errno = ENOENT;
errno = errno_from_Win32LastError();
debug("%s: LookupAccountSid() failed: %d.", __FUNCTION__, GetLastError());
goto cleanup;
}
@ -188,9 +236,8 @@ get_passwd(const wchar_t * user_utf16, PSID sid)
/* convert to utf8, make name lowercase, and assign to output structure*/
_wcslwr_s(user_resolved, wcslen(user_resolved) + 1);
if ((pw.pw_name = utf16_to_utf8(user_resolved)) == NULL ||
(pw.pw_dir = utf16_to_utf8(profile_home_exp)) == NULL ||
(pw.pw_sid = utf16_to_utf8(sid_string)) == NULL) {
reset_pw();
(pw.pw_dir = utf16_to_utf8(profile_home_exp)) == NULL) {
clean_pw();
errno = ENOMEM;
goto cleanup;
}
@ -207,40 +254,84 @@ cleanup:
return ret;
}
static struct passwd*
getpwnam_placeholder(const char* user) {
wchar_t tmp_home[PATH_MAX];
char *pw_name = NULL, *pw_dir = NULL;
struct passwd* ret = NULL;
if (GetWindowsDirectoryW(tmp_home, PATH_MAX) == 0) {
debug3("GetWindowsDirectoryW failed with %d", GetLastError());
errno = EOTHER;
goto cleanup;
}
pw_name = strdup(user);
pw_dir = utf16_to_utf8(tmp_home);
if (!pw_name || !pw_dir) {
errno = ENOMEM;
goto cleanup;
}
pw.pw_name = pw_name;
pw_name = NULL;
pw.pw_dir = pw_dir;
pw_dir = NULL;
ret = &pw;
cleanup:
if (pw_name)
free(pw_name);
if (pw_dir)
free(pw_dir);
return ret;
}
struct passwd*
w32_getpwnam(const char *user_utf8)
{
struct passwd* ret = NULL;
wchar_t * user_utf16 = utf8_to_utf16(user_utf8);
if (user_utf16 == NULL) {
errno = ENOMEM;
return NULL;
}
return get_passwd(user_utf16, NULL);
ret = get_passwd(user_utf16, NULL);
if (ret != NULL)
return ret;
/* check if custom passwd auth is enabled */
{
int lsa_auth_pkg_len = 0;
HKEY reg_key = 0;
REGSAM mask = STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_WOW64_64KEY;
if ((RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\OpenSSH", 0, mask, &reg_key) == ERROR_SUCCESS) &&
(RegQueryValueExW(reg_key, L"LSAAuthenticationPackage", 0, NULL, NULL, &lsa_auth_pkg_len) == ERROR_SUCCESS))
ret = getpwnam_placeholder(user_utf8);
if (reg_key)
RegCloseKey(reg_key);
}
return ret;
}
struct passwd*
w32_getpwuid(uid_t uid)
{
struct passwd* ret = NULL;
HANDLE token = NULL;
TOKEN_USER* info = NULL;
DWORD info_len = 0;
PSID cur_user_sid = NULL;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) == FALSE ||
GetTokenInformation(token, TokenUser, NULL, 0, &info_len) == TRUE ||
(info = (TOKEN_USER*)malloc(info_len)) == NULL ||
GetTokenInformation(token, TokenUser, info, info_len, &info_len) == FALSE)
if ((cur_user_sid = get_user_sid(NULL)) == NULL)
goto cleanup;
ret = get_passwd(NULL, info->User.Sid);
ret = get_passwd(NULL, cur_user_sid);
cleanup:
if (token)
CloseHandle(token);
if (info)
free(info);
if (cur_user_sid)
free(cur_user_sid);
return ret;
}

View File

@ -256,10 +256,6 @@ struct key_translation keys[] = {
static SHORT lastX = 0;
static SHORT lastY = 0;
static wchar_t system32_path[PATH_MAX + 1] = { 0, };
static wchar_t cmd_exe_path[PATH_MAX + 1] = { 0, };
static wchar_t default_shell_path[PATH_MAX + 3] = { 0, }; /* 2 - quotes, 1 - Null terminator */
static wchar_t default_shell_cmd_option[10] = { 0, }; /* for cmd.exe/powershell it is "/c", for bash.exe it is "-c" */
static BOOL is_default_shell_configured = FALSE;
SHORT currentLine = 0;
consoleEvent* head = NULL;
@ -1194,76 +1190,6 @@ ProcessMessages(void* p)
CloseHandle(child_out);
}
wchar_t *
get_default_shell_path()
{
HKEY reg_key = 0;
int tmp_len = PATH_MAX;
errno_t r = 0;
REGSAM mask = STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_WOW64_64KEY;
wchar_t *tmp = malloc(PATH_MAX + 1);
if (!tmp) {
printf_s("%s: out of memory", __func__);
exit(255);
}
memset(tmp, 0, PATH_MAX + 1);
memset(default_shell_path, 0, _countof(default_shell_path));
memset(default_shell_cmd_option, 0, _countof(default_shell_cmd_option));
if ((RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\OpenSSH", 0, mask, &reg_key) == ERROR_SUCCESS) &&
(RegQueryValueExW(reg_key, L"DefaultShell", 0, NULL, (LPBYTE)tmp, &tmp_len) == ERROR_SUCCESS) &&
(tmp)) {
is_default_shell_configured = TRUE;
/* If required, add quotes to the default shell. */
if (tmp[0] != L'"') {
default_shell_path[0] = L'\"';
wcscat_s(default_shell_path, _countof(default_shell_path), tmp);
wcscat_s(default_shell_path, _countof(default_shell_path), L"\"");
} else
wcscat_s(default_shell_path, _countof(default_shell_path), tmp);
/* Fetch the default shell command option.
* For cmd.exe/powershell.exe it is "/c", for bash.exe it is "-c".
* For cmd.exe/powershell.exe/bash.exe, verify if present otherwise auto-populate.
*/
memset(tmp, 0, PATH_MAX + 1);
if ((RegQueryValueExW(reg_key, L"DefaultShellCommandOption", 0, NULL, (LPBYTE)tmp, &tmp_len) == ERROR_SUCCESS)) {
wcscat_s(default_shell_cmd_option, _countof(default_shell_cmd_option), L" ");
wcscat_s(default_shell_cmd_option, _countof(default_shell_cmd_option), tmp);
wcscat_s(default_shell_cmd_option, _countof(default_shell_cmd_option), L" ");
}
}
if (((r = wcsncpy_s(cmd_exe_path, _countof(cmd_exe_path), system32_path, wcsnlen(system32_path, _countof(system32_path)) + 1)) != 0) ||
((r = wcscat_s(cmd_exe_path, _countof(cmd_exe_path), L"\\cmd.exe")) != 0)) {
printf_s("get_default_shell_path(), wcscat_s failed with error: %d.", r);
exit(255);
}
/* if default shell is not configured then use cmd.exe as the default shell */
if (!is_default_shell_configured)
wcscat_s(default_shell_path, _countof(default_shell_path), cmd_exe_path);
if (!default_shell_cmd_option[0]) {
if (wcsstr(default_shell_path, L"cmd.exe") || wcsstr(default_shell_path, L"powershell.exe"))
wcscat_s(default_shell_cmd_option, _countof(default_shell_cmd_option), L" /c ");
else if (wcsstr(default_shell_path, L"bash.exe"))
wcscat_s(default_shell_cmd_option, _countof(default_shell_cmd_option), L" -c ");
}
if (tmp)
free(tmp);
if (reg_key)
RegCloseKey(reg_key);
return default_shell_path;
}
int
start_with_pty(wchar_t *command)
{
@ -1282,6 +1208,11 @@ start_with_pty(wchar_t *command)
exit(255);
}
if (!GetSystemDirectoryW(system32_path, PATH_MAX)) {
printf_s("unable to retrieve system32 path\n");
exit(255);
}
GOTO_CLEANUP_ON_ERR(wcsncpy_s(kernel32_dll_path, _countof(kernel32_dll_path), system32_path, wcsnlen(system32_path, _countof(system32_path)) + 1));
GOTO_CLEANUP_ON_ERR(wcscat_s(kernel32_dll_path, _countof(kernel32_dll_path), L"\\kernel32.dll"));
@ -1311,7 +1242,6 @@ start_with_pty(wchar_t *command)
* Windows PTY sends cursor positions in absolute coordinates starting from <0,0>
* We send a clear screen upfront to simplify client
*/
if(!command)
SendClearScreen(pipe_out);
ZeroMemory(&inputSi, sizeof(STARTUPINFO));
@ -1338,30 +1268,13 @@ start_with_pty(wchar_t *command)
/* disable inheritance on pipe_in*/
GOTO_CLEANUP_ON_FALSE(SetHandleInformation(pipe_in, HANDLE_FLAG_INHERIT, 0));
cmd[0] = L'\0';
GOTO_CLEANUP_ON_ERR(wcscat_s(cmd, MAX_CMD_LEN, get_default_shell_path()));
if (command) {
if(default_shell_cmd_option[0])
GOTO_CLEANUP_ON_ERR(wcscat_s(cmd, MAX_CMD_LEN, default_shell_cmd_option));
GOTO_CLEANUP_ON_ERR(wcscat_s(cmd, MAX_CMD_LEN, command));
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdOutput = pipe_out;
si.hStdError = pipe_err;
} else {
/* Launch the default shell through cmd.exe.
* If we don't launch default shell through cmd.exe then the powershell colors are rendered badly to the ssh client.
/*
* Launch via cmd.exe /c, otherwise known issues exist with color rendering in powershell
*/
if (is_default_shell_configured) {
wchar_t tmp_cmd[PATH_MAX + 1] = {0,};
wcscat_s(tmp_cmd, _countof(tmp_cmd), cmd);
cmd[0] = L'\0';
GOTO_CLEANUP_ON_ERR(wcscat_s(cmd, MAX_CMD_LEN, cmd_exe_path));
GOTO_CLEANUP_ON_ERR(wcscat_s(cmd, MAX_CMD_LEN, L" /c "));
GOTO_CLEANUP_ON_ERR(wcscat_s(cmd, MAX_CMD_LEN, tmp_cmd));
}
}
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));
SetConsoleCtrlHandler(NULL, FALSE);
GOTO_CLEANUP_ON_FALSE(CreateProcess(NULL, cmd, NULL, NULL, TRUE, CREATE_NEW_CONSOLE,
@ -1435,379 +1348,21 @@ cleanup:
return child_exit_code;
}
HANDLE child_pipe_read;
HANDLE child_pipe_write;
DWORD WINAPI
MonitorChild_nopty( _In_ LPVOID lpParameter)
{
WaitForSingleObject(child, INFINITE);
GetExitCodeProcess(child, &child_exit_code);
CloseHandle(pipe_in);
return 0;
}
int
start_withno_pty(wchar_t *command)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
wchar_t *cmd = (wchar_t *)malloc(sizeof(wchar_t) * MAX_CMD_LEN);
SECURITY_ATTRIBUTES sa;
BOOL ret, process_input = FALSE, run_under_cmd = FALSE;
size_t command_len;
char *buf = (char *)malloc(BUFF_SIZE + 1);
DWORD rd = 0, wr = 0, i = 0;
if (cmd == NULL) {
printf_s("ssh-shellhost is out of memory");
exit(255);
}
pipe_in = GetStdHandle(STD_INPUT_HANDLE);
pipe_out = GetStdHandle(STD_OUTPUT_HANDLE);
pipe_err = 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))
return -1;
memset(&sa, 0, sizeof(SECURITY_ATTRIBUTES));
sa.bInheritHandle = TRUE;
/* use the default buffer size, 64K*/
if (!CreatePipe(&child_pipe_read, &child_pipe_write, &sa, 0)) {
printf_s("ssh-shellhost-can't open no pty session, error: %d", GetLastError());
return -1;
}
memset(&si, 0, sizeof(STARTUPINFO));
memset(&pi, 0, sizeof(PROCESS_INFORMATION));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = child_pipe_read;
si.hStdOutput = pipe_out;
si.hStdError = pipe_err;
/* disable inheritance on child_pipe_write and pipe_in*/
GOTO_CLEANUP_ON_FALSE(SetHandleInformation(pipe_in, HANDLE_FLAG_INHERIT, 0));
GOTO_CLEANUP_ON_FALSE(SetHandleInformation(child_pipe_write, HANDLE_FLAG_INHERIT, 0));
/*
* check if the input needs to be processed (ex for CRLF translation)
* input stream needs to be processed when running the command
* within shell processor. This is needed when
* - launching a interactive shell (-nopty)
* ssh -T user@target
* - launching cmd explicity
* ssh user@target cmd
* - executing a cmd command
* ssh user@target dir
* - executing a cmd command within a cmd
* ssh user@target cmd /c dir
*/
if (!command)
process_input = TRUE;
else {
command_len = wcsnlen_s(command, MAX_CMD_LEN);
if ((command_len >= 3 && _wcsnicmp(command, L"cmd", 4) == 0) ||
(command_len >= 7 && _wcsnicmp(command, L"cmd.exe", 8) == 0) ||
(command_len >= 4 && _wcsnicmp(command, L"cmd ", 4) == 0) ||
(command_len >= 8 && _wcsnicmp(command, L"cmd.exe ", 8) == 0))
process_input = TRUE;
}
/* Try launching command as is first */
if (command) {
ret = CreateProcessW(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
if (ret == FALSE) {
/* it was probably this case - ssh user@target dir */
if (GetLastError() == ERROR_FILE_NOT_FOUND)
run_under_cmd = TRUE;
else
goto cleanup;
}
}
else
run_under_cmd = TRUE;
/* if above failed with FILE_NOT_FOUND, try running the provided command under cmd*/
if (run_under_cmd) {
cmd[0] = L'\0';
GOTO_CLEANUP_ON_ERR(wcscat_s(cmd, MAX_CMD_LEN, get_default_shell_path()));
if (command) {
if (default_shell_cmd_option[0])
GOTO_CLEANUP_ON_ERR(wcscat_s(cmd, MAX_CMD_LEN, default_shell_cmd_option));
GOTO_CLEANUP_ON_ERR(wcscat_s(cmd, MAX_CMD_LEN, command));
}
GOTO_CLEANUP_ON_FALSE(CreateProcessW(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi));
/* Create process succeeded when running under cmd. input stream needs to be processed */
process_input = TRUE;
}
/* close unwanted handles*/
CloseHandle(child_pipe_read);
child_pipe_read = INVALID_HANDLE_VALUE;
child = pi.hProcess;
/* monitor child exist */
monitor_thread = CreateThread(NULL, 0, MonitorChild_nopty, NULL, 0, NULL);
if (IS_INVALID_HANDLE(monitor_thread))
goto cleanup;
/* disable Ctrl+C hander in this process*/
SetConsoleCtrlHandler(NULL, TRUE);
if (buf == NULL) {
printf_s("ssh-shellhost is out of memory");
exit(255);
}
/* process data from pipe_in and route appropriately */
while (1) {
rd = wr = i = 0;
buf[0] = L'\0';
GOTO_CLEANUP_ON_FALSE(ReadFile(pipe_in, buf, BUFF_SIZE, &rd, NULL));
if (process_input == FALSE) {
/* write stream directly to child stdin */
GOTO_CLEANUP_ON_FALSE(WriteFile(child_pipe_write, buf, rd, &wr, NULL));
continue;
}
/* else - process input before routing it to child */
while (i < rd) {
/* skip arrow keys */
if ((rd - i >= 3) && (buf[i] == '\033') && (buf[i + 1] == '[') &&
(buf[i + 2] >= 'A') && (buf[i + 2] <= 'D')) {
i += 3;
continue;
}
/* skip tab */
if (buf[i] == '\t') {
i++;
continue;
}
/* Ctrl +C */
if (buf[i] == '\003') {
GOTO_CLEANUP_ON_FALSE(GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0));
in_cmd_len = 0;
i++;
continue;
}
/* for backspace, we need to send space and another backspace for visual erase */
if (buf[i] == '\b' || buf[i] == '\x7f') {
if (in_cmd_len > 0) {
GOTO_CLEANUP_ON_FALSE(WriteFile(pipe_out, "\b \b", 3, &wr, NULL));
in_cmd_len--;
}
i++;
continue;
}
/* For CR and LF */
if ((buf[i] == '\r') || (buf[i] == '\n')) {
/* TODO - do a much accurate mapping */
if ((buf[i] == '\r') && ((i == rd - 1) || (buf[i + 1] != '\n')))
buf[i] = '\n';
GOTO_CLEANUP_ON_FALSE(WriteFile(pipe_out, buf + i, 1, &wr, NULL));
in_cmd[in_cmd_len] = buf[i];
in_cmd_len++;
GOTO_CLEANUP_ON_FALSE(WriteFile(child_pipe_write, in_cmd, in_cmd_len, &wr, NULL));
in_cmd_len = 0;
i++;
continue;
}
GOTO_CLEANUP_ON_FALSE(WriteFile(pipe_out, buf + i, 1, &wr, NULL));
in_cmd[in_cmd_len] = buf[i];
in_cmd_len++;
if (in_cmd_len == MAX_CMD_LEN - 1) {
GOTO_CLEANUP_ON_FALSE(WriteFile(child_pipe_write, in_cmd, in_cmd_len, &wr, NULL));
in_cmd_len = 0;
}
i++;
}
}
cleanup:
/* close child's stdin first */
if(!IS_INVALID_HANDLE(child_pipe_write))
CloseHandle(child_pipe_write);
if (!IS_INVALID_HANDLE(monitor_thread)) {
WaitForSingleObject(monitor_thread, INFINITE);
CloseHandle(monitor_thread);
}
if (!IS_INVALID_HANDLE(child))
TerminateProcess(child, 0);
if (buf != NULL)
free(buf);
if (cmd != NULL)
free(cmd);
return child_exit_code;
}
static void* xmalloc(size_t size) {
void* ptr;
if ((ptr = malloc(size)) == NULL) {
printf_s("out of memory");
exit(EXIT_FAILURE);
}
return ptr;
}
/* set user environment variables from user profile */
static void setup_session_user_vars()
{
/* retrieve and set env variables. */
HKEY reg_key = 0;
wchar_t name[256];
wchar_t userprofile_path[PATH_MAX + 1] = { 0, }, 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;
DWORD len = GetCurrentDirectory(_countof(userprofile_path), userprofile_path);
if (len > 0) {
SetEnvironmentVariableW(L"USERPROFILE", userprofile_path);
swprintf_s(path, _countof(path), L"%s\\AppData\\Local", userprofile_path);
SetEnvironmentVariableW(L"LOCALAPPDATA", path);
swprintf_s(path, _countof(path), L"%s\\AppData\\Roaming", userprofile_path);
SetEnvironmentVariableW(L"APPDATA", path);
}
ret = RegOpenKeyExW(HKEY_CURRENT_USER, L"Environment", 0, KEY_QUERY_VALUE, &reg_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 * 2;
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);
RevertToSelf();
}
int b64_pton(char const *src, u_char *target, size_t targsize);
/* shellhost.exe <cmdline to be executed with PTY support>*/
int
wmain(int ac, wchar_t **av)
{
int pty_requested = 0;
wchar_t *cmd = NULL, *cmd_b64 = NULL;
JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info;
wchar_t *exec_command;
_set_invalid_parameter_handler(my_invalid_parameter_handler);
if ((ac == 1) || (ac == 2 && wcscmp(av[1], L"-nopty"))) {
pty_requested = 1;
cmd_b64 = ac == 2? av[1] : NULL;
} else if (ac <= 3 && wcscmp(av[1], L"-nopty") == 0)
cmd_b64 = ac == 3? av[2] : NULL;
else {
printf_s("ssh-shellhost received unexpected input arguments");
return -1;
}
setup_session_user_vars();
/* decode cmd_b64*/
if (cmd_b64) {
char *cmd_b64_utf8, *cmd_utf8;
if ((cmd_b64_utf8 = utf16_to_utf8(cmd_b64)) == NULL ||
/* strlen(b64) should be sufficient for decoded length */
(cmd_utf8 = malloc(strlen(cmd_b64_utf8))) == NULL) {
printf_s("ssh-shellhost - out of memory");
return -1;
}
memset(cmd_utf8, 0, strlen(cmd_b64_utf8));
if (b64_pton(cmd_b64_utf8, cmd_utf8, strlen(cmd_b64_utf8)) == -1 ||
(cmd = utf8_to_utf16(cmd_utf8)) == NULL) {
printf_s("ssh-shellhost encountered an internal error while decoding base64 cmdline");
return -1;
}
free(cmd_b64_utf8);
free(cmd_utf8);
}
ZeroMemory(system32_path, _countof(system32_path));
if (!GetSystemDirectory(system32_path, _countof(system32_path))) {
printf_s("GetSystemDirectory failed");
if (ac == 1) {
printf("usage: shellhost.exe <cmdline to be executed with PTY support>\n");
exit(255);
}
/* assign to job object */
if ((job = CreateJobObjectW(NULL, NULL)) == NULL) {
printf_s("cannot create job object, error: %d", GetLastError());
return -1;
}
/* get past shellhost.exe in commandline */
exec_command = wcsstr(GetCommandLineW(), L"shellhost.exe") + wcslen(L"shellhost.exe") + 1;
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 (!SetInformationJobObject(job, JobObjectExtendedLimitInformation, &job_info, sizeof(job_info)) ||
!AssignProcessToJobObject(job, GetCurrentProcess())) {
printf_s("cannot associate job object: %d", GetLastError());
return -1;
}
if (pty_requested)
return start_with_pty(cmd);
else
return start_withno_pty(cmd);
return start_with_pty(exec_command);
}

View File

@ -54,20 +54,11 @@ check_secure_file_permission(const char *input_path, struct passwd * pw)
PACL dacl = NULL;
DWORD error_code = ERROR_SUCCESS;
BOOL is_valid_sid = FALSE, is_valid_acl = FALSE;
struct passwd * pwd = pw;
char *bad_user = NULL;
int ret = 0;
if (pwd == NULL)
if ((pwd = getpwuid(0)) == NULL)
fatal("getpwuid failed.");
if (ConvertStringSidToSid(pwd->pw_sid, &user_sid) == FALSE ||
(IsValidSid(user_sid) == FALSE)) {
debug3("failed to retrieve sid of user %s", pwd->pw_name);
ret = -1;
if ((user_sid = get_user_sid(pw ? pw->pw_name : NULL)) == NULL)
goto cleanup;
}
if ((path_utf16 = resolved_path_utf16(input_path)) == NULL) {
ret = -1;
@ -143,7 +134,7 @@ cleanup:
if (pSD)
LocalFree(pSD);
if (user_sid)
LocalFree(user_sid);
free(user_sid);
if(path_utf16)
free(path_utf16);
return ret;

3
misc.c
View File

@ -302,9 +302,6 @@ pwcopy(struct passwd *pw)
#endif
copy->pw_dir = xstrdup(pw->pw_dir);
copy->pw_shell = xstrdup(pw->pw_shell);
#ifdef WINDOWS
copy->pw_sid = xstrdup(pw->pw_sid);
#endif /* WINDOWS */
return copy;
}

376
session.c
View File

@ -371,25 +371,113 @@ xauth_valid_string(const char *s)
*/
#define UTF8_TO_UTF16_FATAL(o, i) do { \
#define UTF8_TO_UTF16_WITH_CLEANUP(o, i) do { \
if (o != NULL) free(o); \
if ((o = utf8_to_utf16(i)) == NULL) \
fatal("%s, out of memory", __func__); \
goto cleanup; \
} while (0)
static void setup_session_vars(Session* s) {
#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);
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, &reg_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_vars(Session* s)
{
wchar_t *pw_dir_w = NULL, *tmp = NULL;
char buf[256];
wchar_t wbuf[256];
char* laddr;
int ret = -1;
struct ssh *ssh = active_state; /* XXX */
UTF8_TO_UTF16_FATAL(pw_dir_w, s->pw->pw_dir);
UTF8_TO_UTF16_FATAL(tmp, s->pw->pw_name);
UTF8_TO_UTF16_WITH_CLEANUP(pw_dir_w, s->pw->pw_dir);
UTF8_TO_UTF16_WITH_CLEANUP(tmp, s->pw->pw_name);
SetEnvironmentVariableW(L"USERNAME", tmp);
if (s->display) {
UTF8_TO_UTF16_FATAL(tmp, s->display);
UTF8_TO_UTF16_WITH_CLEANUP(tmp, s->display);
SetEnvironmentVariableW(L"DISPLAY", tmp);
}
SetEnvironmentVariableW(L"USERPROFILE", pw_dir_w);
@ -415,7 +503,7 @@ static void setup_session_vars(Session* s) {
SetEnvironmentVariableA("SSH_CONNECTION", buf);
if (original_command) {
UTF8_TO_UTF16_FATAL(tmp, original_command);
UTF8_TO_UTF16_WITH_CLEANUP(tmp, original_command);
SetEnvironmentVariableW(L"SSH_ORIGINAL_COMMAND", tmp);
}
@ -423,39 +511,35 @@ static void setup_session_vars(Session* s) {
SetEnvironmentVariableA("TERM", s->term);
if (!s->is_subsystem) {
UTF8_TO_UTF16_FATAL(tmp, s->pw->pw_name);
UTF8_TO_UTF16_WITH_CLEANUP(tmp, s->pw->pw_name);
_snwprintf(wbuf, sizeof(wbuf)/2, L"%ls@%ls $P$G", tmp, _wgetenv(L"COMPUTERNAME"));
SetEnvironmentVariableW(L"PROMPT", wbuf);
}
/* setup any user specific env variables */
setup_session_user_vars(pw_dir_w);
ret = 0;
cleanup:
free(pw_dir_w);
free(tmp);
return ret;
}
char* w32_programdir();
int register_child(void* child, unsigned long pid);
int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) {
int pipein[2], pipeout[2], pipeerr[2], r;
char *exec_command = NULL, *progdir = w32_programdir(), *cmd = NULL, *shell_host = NULL, *command_b64 = NULL;
int pipein[2], pipeout[2], pipeerr[2], r, ret = -1;
char *progdir = w32_programdir();
wchar_t *exec_command_w = NULL;
const char *sftp_exe = "sftp-server.exe", *argp = NULL;
size_t command_b64_len = 0;
PROCESS_INFORMATION pi;
STARTUPINFOW si;
BOOL create_process_ret_val;
HANDLE hToken = INVALID_HANDLE_VALUE;
extern int debug_flag;
if (s->is_subsystem >= SUBSYSTEM_INT_SFTP_ERROR) {
error("This service allows sftp connections only.\n");
fflush(NULL);
exit(1);
}
char *command_enhanced = NULL, *exec_command = NULL;
HANDLE job = NULL;
/* Create three pipes for stdin, stdout and stderr */
if (pipe(pipein) == -1 || pipe(pipeout) == -1 || pipe(pipeerr) == -1)
fatal("%s: cannot create pipe: %.100s", __func__, strerror(errno));
goto cleanup;
set_nonblock(pipein[0]);
set_nonblock(pipein[1]);
@ -469,86 +553,142 @@ int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) {
fcntl(pipeerr[0], F_SETFD, FD_CLOEXEC);
/* setup Environment varibles */
setup_session_vars(s);
do {
static int environment_set = 0;
if (environment_set)
break;
if (setup_session_vars(s) != 0)
goto cleanup;
environment_set = 1;
} while (0);
if (!in_chroot)
chdir(s->pw->pw_dir);
/* prepare exec - path used with CreateProcess() */
#define CMDLINE_APPEND(P, S) \
do { \
int _S_len = strlen(S); \
memcpy((P), (S), _S_len); \
(P) += _S_len; \
} while(0)
/* 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)) {
/* relative or absolute */
if (command == NULL || command[0] == '\0')
fatal("expecting command for a subsystem");
int en_size;
char* p;
if (command[1] == ':') /* absolute */
exec_command = xstrdup(command);
else {/*relative*/
const int command_len = strlen(progdir) + 1 + strlen(command) + (strlen(sftp_exe) - strlen(INTERNAL_SFTP_NAME));
exec_command = malloc(command_len);
if (exec_command == NULL)
fatal("%s, out of memory", __func__);
cmd = exec_command;
memcpy(cmd, progdir, strlen(progdir));
cmd += strlen(progdir);
*cmd++ = '\\';
/* In windows, INTERNAL_SFTP is supported via sftp-server.exe.
* This is a deviation from the UNIX implementation that hosts sftp-server within sshd.
* If sftp-server were to be hosted within sshd for Windows, following would be needed
* - Impersonate client user
* - call sftp-server-main
*
* SSHD service account would need impersonate privilege to impersonate client user,
* thereby needing elevation of SSHD account privileges
* Apart from slight performance gain (by hosting sftp in process), there isn't a clear
* gain with this option over using and spawning sftp-server.exe.
* Hence going with the later option.
*/
if(IS_INTERNAL_SFTP(command)) {
memcpy(cmd, sftp_exe, strlen(sftp_exe) + 1);
cmd += strlen(sftp_exe);
// copy the arguments (if any).
if(strlen(command) > strlen(INTERNAL_SFTP_NAME)) {
argp = (char*)command + strlen(INTERNAL_SFTP_NAME);
memcpy(cmd, argp, strlen(argp)+1);
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
memcpy(cmd, command, strlen(command) + 1);
CMDLINE_APPEND(p, command);
*p = '\0';
command = command_enhanced;
break;
}
} else {
/*
* contruct %programdir%\ssh-shellhost.exe <-nopty> base64encoded(command)
* command is base64 encoded to preserve original special charecters like '"'
* else they will get lost in CreateProcess translation
*/
shell_host = pty ? "ssh-shellhost.exe " : "ssh-shellhost.exe -nopty ";
} 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) {
/* accomodate bas64 encoding bloat and null terminator */
command_b64_len = ((strlen(command) + 2) / 3) * 4 + 1;
if ((command_b64 = malloc(command_b64_len)) == NULL ||
b64_ntop(command, strlen(command), command_b64, command_b64_len) == -1)
fatal("%s, error encoding session 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, "\"");
}
exec_command = malloc(strlen(progdir) + 1 + strlen(shell_host) + (command_b64 ? strlen(command_b64): 0) + 1);
if (exec_command == NULL)
fatal("%s, out of memory", __func__);
cmd = exec_command;
memcpy(cmd, progdir, strlen(progdir));
cmd += strlen(progdir);
*cmd++ = '\\';
memcpy(cmd, shell_host, strlen(shell_host));
cmd += strlen(shell_host);
if (command_b64) {
memcpy(cmd, command_b64, strlen(command_b64));
cmd += strlen(command_b64);
}
*cmd = '\0';
*p = '\0';
}
/* 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;
@ -563,16 +703,36 @@ int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) {
si.lpDesktop = NULL;
debug("Executing command: %s", exec_command);
UTF8_TO_UTF16_FATAL(exec_command_w, exec_command);
if ((exec_command_w = utf8_to_utf16(exec_command)) == NULL)
goto cleanup;
create_process_ret_val = CreateProcessW(NULL, exec_command_w, NULL, NULL, TRUE,
DETACHED_PROCESS, NULL, NULL,
&si, &pi);
if (!create_process_ret_val)
fatal("ERROR. Cannot create process (%u).\n", GetLastError());
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);
}
@ -597,8 +757,19 @@ int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) {
else
session_set_fds(ssh, s, pipein[1], pipeout[0], pipeerr[0], s->is_subsystem, 1); /* tty interactive session */
ret = 0;
cleanup:
if (!command_enhanced)
free(command_enhanced);
if (!exec_command)
free(exec_command);
if (!exec_command_w)
free(exec_command_w);
return 0;
if (job)
CloseHandle(job);
return ret;
}
int
@ -1542,7 +1713,16 @@ safely_chroot(const char *path, uid_t uid)
fatal("chroot path does not begin at root");
if (strlen(path) >= sizeof(component))
fatal("chroot path too long");
#ifndef WINDOWS
#ifdef WINDOWS
/* ensure chroot path exists and is a directory */
if (stat(path, &st) != 0)
fatal("%s: stat(\"%s\"): %s", __func__,
path, strerror(errno));
if (!S_ISDIR(st.st_mode))
fatal("chroot path %s is not a directory",
path);
#else
/*
* Descend the path, checking that each component is a
* root-owned directory with strict permissions.