session process path changes (#319)

Added utility to build session process command line - this accounts for restrictions from various shells. With these changes, scp and sftp-server are expected to be machine wide PATH if a custom shell (other than cmd.exe) is defined. Added comprehensive test cases.
Fixed issue with USERNAME env variable containing domain prefix too.

PowerShell/Win32-OpenSSH#1165
PowerShell/Win32-OpenSSH#1165
PowerShell/Win32-OpenSSH#1171
This commit is contained in:
Manoj Ampalam 2018-06-04 12:16:13 -07:00 committed by GitHub
parent 710050b186
commit 1e0c864707
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 321 additions and 119 deletions

View File

@ -1720,3 +1720,178 @@ cleanup:
return ret;
}
/* builds session commandline. returns NULL with errno set on failure, caller should free returned string */
char*
build_session_commandline(const char *shell, const char* shell_arg, const char *command, int pty)
{
enum sh_type { SH_CMD, SH_PS, SH_WSL_BASH, SH_CYGWIN, SH_OTHER } shell_type = SH_OTHER;
enum cmd_type { CMD_OTHER, CMD_SFTP, CMD_SCP } command_type = CMD_OTHER;
char *progdir = w32_programdir(), *cmd_sp = NULL, *cmdline = NULL, *ret = NULL, *p;
int len, progdir_len = (int)strlen(progdir);
#define CMDLINE_APPEND(P, S) \
do { \
int _S_len = (int)strlen(S); \
memcpy((P), (S), _S_len); \
(P) += _S_len; \
} while(0)
/* get shell type */
if (strstr(shell, "system32\\cmd"))
shell_type = SH_CMD;
else if (strstr(shell, "powershell"))
shell_type = SH_PS;
else if (strstr(shell, "system32\\bash"))
shell_type = SH_WSL_BASH;
else if (strstr(shell, "cygwin"))
shell_type = SH_CYGWIN;
/* special case where incoming command needs to be adjusted */
do {
/*
* identify scp and sftp sessions
* we want to launch scp and sftp executables from the same binary directory
* that sshd is hosted in. This will facilitate hosting and evaluating
* multiple versions of OpenSSH at the same time.
*
* currently we can only accomodate this for cmd.exe, since cmd.exe simply executes
* its commandline without applying CRT or shell specific rules
*
* Ex.
* this works
* cmd /c "c:\program files\sftp" -d
* this following wouldn't work in powershell, cygwin's or WSL bash unless we put
* some shell specific rules
* powershell -c "c:\program files\scp" -t
* cygwin\bash -c "c:\program files\scp" -t
* bash -c "c:\program files\scp" -t
*
* for now, for all non-cmd shells, we launch scp and sftp-server directly -
* shell -c "scp.exe -t"
* note that .exe extension and case matters for WSL bash
* note that double quotes matter for WSL and Cygwin bash, they dont matter for PS
*
* consequence -
* for non-cmd shells - sftp and scp installation path is expected to be in machine wide PATH
*
*/
int command_len;
const char *command_args = NULL;
if (!command)
break;
command_len = (int)strlen(command);
if (command_len >= 13 && _memicmp(command, "internal-sftp", 13) == 0) {
command_type = CMD_SFTP;
command_args = command + 13;
} else if (command_len >= 11 && _memicmp(command, "sftp-server", 11) == 0) {
command_type = CMD_SFTP;
/* account for possible .exe extension */
if (command_len >= 15 && _memicmp(command + 11, ".exe", 4) == 0)
command_args = command + 15;
else
command_args = command + 11;
} else if (command_len >= 3 && _memicmp(command, "scp", 3) == 0) {
command_type = CMD_SCP;
/* account for possible .exe extension */
if (command_len >= 7 && _memicmp(command + 3, ".exe", 4) == 0)
command_args = command + 7;
else
command_args = command + 3;
}
if (command_type == CMD_OTHER)
break;
len = 0;
len += progdir_len + 4; /* account for " around */
len += command_len + 4; /* account for possible .exe addition */
if ((cmd_sp = malloc(len)) == NULL) {
errno = ENOMEM;
goto done;
}
p = cmd_sp;
if (shell_type == SH_CMD) {
CMDLINE_APPEND(p, "\"");
CMDLINE_APPEND(p, progdir);
if (command_type == CMD_SCP)
CMDLINE_APPEND(p, "\\scp.exe\"");
else
CMDLINE_APPEND(p, "\\sftp-server.exe\"");
} else {
if (command_type == CMD_SCP)
CMDLINE_APPEND(p, "scp.exe");
else
CMDLINE_APPEND(p, "sftp-server.exe");
}
CMDLINE_APPEND(p, command_args);
*p = '\0';
command = cmd_sp;
} while (0);
len = 0;
if (pty)
len += progdir_len + (int)strlen("ssh-shellhost.exe") + 5;
len +=(int) strlen(shell) + 3;/* 3 for " around shell path and trailing space */
if (command) {
len += 15; /* for shell command argument, typically -c or /c */
len += (int)strlen(command) + 5; /* 5 for possible " around command and null term*/
}
if ((cmdline = malloc(len)) == NULL) {
errno = ENOMEM;
goto done;
}
p = cmdline;
if (pty) {
CMDLINE_APPEND(p, "\"");
CMDLINE_APPEND(p, progdir);
CMDLINE_APPEND(p, "\\ssh-shellhost.exe\" ");
}
CMDLINE_APPEND(p, "\"");
CMDLINE_APPEND(p, shell);
CMDLINE_APPEND(p, "\"");
if (command) {
if (shell_arg) {
CMDLINE_APPEND(p, " ");
CMDLINE_APPEND(p, shell_arg);
CMDLINE_APPEND(p, " ");
}
else if (shell_type == SH_CMD)
CMDLINE_APPEND(p, " /c ");
else
CMDLINE_APPEND(p, " -c ");
/* bash type shells require " decoration around command*/
if (shell_type == SH_WSL_BASH || shell_type == SH_CYGWIN)
CMDLINE_APPEND(p, "\"");
CMDLINE_APPEND(p, command);
if (shell_type == SH_WSL_BASH || shell_type == SH_CYGWIN)
CMDLINE_APPEND(p, "\"");
}
*p = '\0';
ret = cmdline;
cmdline = NULL;
done:
if (cmd_sp)
free(cmd_sp);
if (cmdline)
free(cmdline);
return ret;
}

View File

@ -54,3 +54,4 @@ int is_absolute_path(const char *);
int file_in_chroot_jail(HANDLE, const char*);
PSID get_user_sid(char*);
int am_system();
char* build_session_commandline(const char *, const char *, const char *, int );

View File

@ -94,6 +94,7 @@ set_defaultshell()
if ((command_option_local = utf16_to_utf8(option_buf)) == NULL)
goto cleanup;
convertToBackslash(pw_shellpath_local);
pw_shellpath = pw_shellpath_local;
pw_shellpath_local = NULL;
shell_command_option = command_option_local;

View File

@ -311,6 +311,137 @@ test_chroot()
//_wsystem(L"RD /S /Q chroot-testdir >NUL 2>&1");
}
void
test_build_session_commandline()
{
char *progdir = w32_programdir(), *out, buf[PATH_MAX*2], shellhost_path[PATH_MAX];
shellhost_path[0] = '\0';
strcat(shellhost_path, "\"");
strcat(shellhost_path, progdir);
strcat(shellhost_path, "\\ssh-shellhost.exe\"");
int shellhost_path_len = (int)strlen(shellhost_path);
TEST_START("default interactive session tests");
out = build_session_commandline("c:\\system32\\cmd.exe", NULL, NULL, 0);
ASSERT_STRING_EQ(out, "\"c:\\system32\\cmd.exe\"");
out = build_session_commandline("c:\\system32\\cmd.exe", NULL, NULL, 1);
ASSERT_STRING_EQ(out + shellhost_path_len + 1, "\"c:\\system32\\cmd.exe\"");
out[shellhost_path_len] = '\0';
ASSERT_STRING_EQ(out, shellhost_path);
TEST_DONE();
TEST_START("cmd shell tests");
buf[0] = '\0';
strcat(buf, "\"c:\\system32\\cmd.exe\" /c \"");
strcat(buf, progdir);
int len_pg = strlen(buf);
out = build_session_commandline("c:\\system32\\cmd.exe", NULL, "internal-sftp -arg", 0);
buf[len_pg] = '\0';
strcat(buf, "\\sftp-server.exe\" -arg");
ASSERT_STRING_EQ(out, buf);
out = build_session_commandline("c:\\system32\\cmd.exe", NULL, "SFTP-server.exe -arg", 0);
buf[len_pg] = '\0';
strcat(buf, "\\sftp-server.exe\" -arg");
ASSERT_STRING_EQ(out, buf);
out = build_session_commandline("c:\\system32\\cmd.exe", NULL, "sftp-SERVER -arg", 0);
buf[len_pg] = '\0';
strcat(buf, "\\sftp-server.exe\" -arg");
ASSERT_STRING_EQ(out, buf);
out = build_session_commandline("c:\\system32\\cmd.exe", NULL, "sCp -arg", 0);
buf[len_pg] = '\0';
strcat(buf, "\\scp.exe\" -arg");
ASSERT_STRING_EQ(out, buf);
out = build_session_commandline("c:\\system32\\cmd.exe", NULL, "mycommand -arg", 1);
ASSERT_STRING_EQ(out + shellhost_path_len + 1, "\"c:\\system32\\cmd.exe\" /c mycommand -arg");
out[shellhost_path_len] = '\0';
ASSERT_STRING_EQ(out, shellhost_path);
free(out);
TEST_DONE();
TEST_START("wsl bash shell tests");
out = build_session_commandline("c:\\system32\\bash.exe", NULL, "internal-sftp -arg", 0);
ASSERT_STRING_EQ(out, "\"c:\\system32\\bash.exe\" -c \"sftp-server.exe -arg\"");
free(out);
out = build_session_commandline("c:\\system32\\bash", NULL, "internal-sftp -arg", 0);
ASSERT_STRING_EQ(out, "\"c:\\system32\\bash\" -c \"sftp-server.exe -arg\"");
free(out);
out = build_session_commandline("c:\\system32\\bash", NULL, "sFTP-server -arg", 0);
ASSERT_STRING_EQ(out, "\"c:\\system32\\bash\" -c \"sftp-server.exe -arg\"");
free(out);
out = build_session_commandline("c:\\system32\\bash", NULL, "scP -arg", 0);
ASSERT_STRING_EQ(out, "\"c:\\system32\\bash\" -c \"scp.exe -arg\"");
free(out);
out = build_session_commandline("c:\\system32\\bash", "-custom", "mycommand -arg", 1);
ASSERT_STRING_EQ(out + shellhost_path_len + 1, "\"c:\\system32\\bash\" -custom \"mycommand -arg\"");
out[shellhost_path_len] = '\0';
ASSERT_STRING_EQ(out, shellhost_path);
free(out);
TEST_DONE();
TEST_START("cygwin bash shell tests");
out = build_session_commandline("c:\\cygwin\\bash.exe", NULL, "internal-sftp -arg", 0);
ASSERT_STRING_EQ(out, "\"c:\\cygwin\\bash.exe\" -c \"sftp-server.exe -arg\"");
free(out);
out = build_session_commandline("c:\\cygwin\\bash", NULL, "sftp-server -arg", 0);
ASSERT_STRING_EQ(out, "\"c:\\cygwin\\bash\" -c \"sftp-server.exe -arg\"");
free(out);
out = build_session_commandline("c:\\cygwin\\bash", NULL, "sftp-seRVer.exe -arg", 0);
ASSERT_STRING_EQ(out, "\"c:\\cygwin\\bash\" -c \"sftp-server.exe -arg\"");
free(out);
out = build_session_commandline("c:\\cygwin\\bash", NULL, "sCp -arg", 0);
ASSERT_STRING_EQ(out, "\"c:\\cygwin\\bash\" -c \"scp.exe -arg\"");
free(out);
out = build_session_commandline("c:\\cygwin\\bash", "-custom", "mycommand -arg", 1);
ASSERT_STRING_EQ(out + shellhost_path_len + 1, "\"c:\\cygwin\\bash\" -custom \"mycommand -arg\"");
out[shellhost_path_len] = '\0';
ASSERT_STRING_EQ(out, shellhost_path);
free(out);
TEST_DONE();
TEST_START("powershell shell tests");
out = build_session_commandline("c:\\powershell.exe", NULL, "internal-sftp -arg", 0);
ASSERT_STRING_EQ(out, "\"c:\\powershell.exe\" -c sftp-server.exe -arg");
free(out);
out = build_session_commandline("c:\\powershell", NULL, "sftp-server -arg", 0);
ASSERT_STRING_EQ(out, "\"c:\\powershell\" -c sftp-server.exe -arg");
free(out);
out = build_session_commandline("c:\\powershell.exe", NULL, "sftp-sERver.exe -arg", 0);
ASSERT_STRING_EQ(out, "\"c:\\powershell.exe\" -c sftp-server.exe -arg");
free(out);
out = build_session_commandline("c:\\powershell.exe", NULL, "scP -arg", 0);
ASSERT_STRING_EQ(out, "\"c:\\powershell.exe\" -c scp.exe -arg");
free(out);
out = build_session_commandline("c:\\powershell.exe", "-custom", "mycommand -arg", 1);
ASSERT_STRING_EQ(out + shellhost_path_len + 1, "\"c:\\powershell.exe\" -custom mycommand -arg");
out[shellhost_path_len] = '\0';
ASSERT_STRING_EQ(out, shellhost_path);
free(out);
TEST_DONE();
TEST_START("other shell tests");
out = build_session_commandline("c:\\myshell.exe", NULL, "internal-sftp -arg", 0);
ASSERT_STRING_EQ(out, "\"c:\\myshell.exe\" -c sftp-server.exe -arg");
free(out);
out = build_session_commandline("c:\\myshell", NULL, "sftp-server -arg", 0);
ASSERT_STRING_EQ(out, "\"c:\\myshell\" -c sftp-server.exe -arg");
free(out);
out = build_session_commandline("c:\\myshell", NULL, "sftp-seRVer.exe -arg", 0);
ASSERT_STRING_EQ(out, "\"c:\\myshell\" -c sftp-server.exe -arg");
free(out);
out = build_session_commandline("c:\\myshell", NULL, "sCp -arg", 0);
ASSERT_STRING_EQ(out, "\"c:\\myshell\" -c scp.exe -arg");
free(out);
out = build_session_commandline("c:\\myshell", "-custom", "mycommand -arg", 1);
ASSERT_STRING_EQ(out + shellhost_path_len + 1, "\"c:\\myshell\" -custom mycommand -arg");
out[shellhost_path_len] = '\0';
ASSERT_STRING_EQ(out, shellhost_path);
free(out);
TEST_DONE();
}
void
miscellaneous_tests()
{
@ -321,4 +452,5 @@ miscellaneous_tests()
test_realpath();
test_statvfs();
test_chroot();
test_build_session_commandline();
}

131
session.c
View File

@ -468,13 +468,15 @@ setup_session_vars(Session* s)
wchar_t *pw_dir_w = NULL, *tmp = NULL;
char buf[256];
wchar_t wbuf[256];
char* laddr;
char *laddr, *c;
int ret = -1;
struct ssh *ssh = active_state; /* XXX */
UTF8_TO_UTF16_WITH_CLEANUP(pw_dir_w, s->pw->pw_dir);
UTF8_TO_UTF16_WITH_CLEANUP(tmp, s->pw->pw_name);
/* skip domain part (if there) while setting USERNAME */
c = strchr(s->pw->pw_name, '\\');
UTF8_TO_UTF16_WITH_CLEANUP(tmp, c ? c + 1 : s->pw->pw_name);
SetEnvironmentVariableW(L"USERNAME", tmp);
if (s->display) {
UTF8_TO_UTF16_WITH_CLEANUP(tmp, s->display);
@ -527,15 +529,15 @@ cleanup:
return ret;
}
char* w32_programdir();
int register_child(void* child, unsigned long pid);
char* build_session_commandline(const char *, const char *, const char *, int);
int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) {
int pipein[2], pipeout[2], pipeerr[2], r, ret = -1;
char *progdir = w32_programdir();
wchar_t *exec_command_w = NULL;
char *command_enhanced = NULL, *exec_command = NULL;
char *exec_command = NULL;
HANDLE job = NULL;
extern char* shell_command_option;
/* Create three pipes for stdin, stdout and stderr */
if (pipe(pipein) == -1 || pipe(pipeout) == -1 || pipe(pipeerr) == -1)
@ -568,119 +570,12 @@ int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) {
if (!in_chroot)
chdir(s->pw->pw_dir);
#define CMDLINE_APPEND(P, S) \
do { \
int _S_len = strlen(S); \
memcpy((P), (S), _S_len); \
(P) += _S_len; \
} while(0)
if (s->is_subsystem >= SUBSYSTEM_INT_SFTP_ERROR)
command = "echo This service allows sftp connections only.";
/* special cases where incoming command needs to be adjusted */
do {
if (s->is_subsystem >= SUBSYSTEM_INT_SFTP_ERROR) {
command = "echo This service allows sftp connections only.";
break;
}
/* if scp or sftp - add module path if command is not absolute */
if (s->is_subsystem || (command && memcmp(command, "scp", 3) == 0)) {
int en_size;
char* p;
if (!command || command[0] == '\0') {
error("expecting command for subsystem or scp");
errno = EOTHER;
return -1;
}
/* if absolute skip further logic */
if (command[1] == ':')
break;
/* account for max possible enhanced path */
en_size = PATH_MAX + 1 + strlen(command) ;
if ((command_enhanced = malloc(en_size)) == NULL) {
errno = ENOMEM;
goto cleanup;
}
p = command_enhanced;
CMDLINE_APPEND(p, progdir);
CMDLINE_APPEND(p, "\\");
/* since Windows does not support fork, launch sftp-server.exe for internal_sftp */
if (IS_INTERNAL_SFTP(command)) {
CMDLINE_APPEND(p, "sftp-server.exe");
/* add subsystem arguments if any */
CMDLINE_APPEND(p, command + strlen(INTERNAL_SFTP_NAME));
} else
CMDLINE_APPEND(p, command);
*p = '\0';
command = command_enhanced;
break;
}
} while (0);
/* build command line to be executed */
{
/* max possible cmdline size - account for shellhost path, shell path and command */
int max_cmdline_size = 2 * PATH_MAX + (command ? strlen(command) + 1 : 1) + 1;
char* p;
enum sh_type { SH_CMD, SH_PS, SH_BASH, SH_OTHER } shell_type = SH_OTHER;
extern char* shell_command_option;
if ((exec_command = malloc(max_cmdline_size)) == NULL) {
errno = ENOMEM;
goto cleanup;
}
p = exec_command;
if (strstr(s->pw->pw_shell, "system32\\cmd.exe"))
shell_type = SH_CMD;
else if (strstr(s->pw->pw_shell, "powershell"))
shell_type = SH_PS;
else if (strstr(s->pw->pw_shell, "bash"))
shell_type = SH_BASH;
else if (strstr(s->pw->pw_shell, "cygwin"))
shell_type = SH_BASH;
/* build command line */
/* For PTY - launch via ssh-shellhost.exe */
if (pty) {
CMDLINE_APPEND(p,"\"");
CMDLINE_APPEND(p, progdir);
CMDLINE_APPEND(p, "\\ssh-shellhost.exe\" ");
}
/* Add shell */
CMDLINE_APPEND(p, "\"");
CMDLINE_APPEND(p, s->pw->pw_shell);
CMDLINE_APPEND(p, "\"");
/* Add command option and command*/
if (command) {
if (shell_command_option) {
CMDLINE_APPEND(p, " ");
CMDLINE_APPEND(p, shell_command_option);
CMDLINE_APPEND(p, " ");
} else if (shell_type == SH_CMD)
CMDLINE_APPEND(p, " /c ");
else
CMDLINE_APPEND(p, " -c ");
if (shell_type == SH_BASH)
CMDLINE_APPEND(p, "\"");
CMDLINE_APPEND(p, command);
if (shell_type == SH_BASH)
CMDLINE_APPEND(p, "\"");
}
*p = '\0';
}
exec_command = build_session_commandline(s->pw->pw_shell, shell_command_option, command, pty);
if (exec_command == NULL)
goto cleanup;
/* start the process */
{
@ -760,8 +655,6 @@ do { \
ret = 0;
cleanup:
if (!command_enhanced)
free(command_enhanced);
if (!exec_command)
free(exec_command);
if (!exec_command_w)