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:
parent
a479737cd5
commit
3fb0c252c3
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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*);
|
|
@ -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, ®_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, ®_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;
|
||||
}
|
||||
|
|
|
@ -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, ®_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, ®_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);
|
||||
}
|
||||
|
|
|
@ -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
3
misc.c
|
@ -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
376
session.c
|
@ -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, ®_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.
|
||||
|
|
Loading…
Reference in New Issue