Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,25 @@ allowed_user(struct ssh *ssh, struct passwd * pw)
*/
if (options.chroot_directory == NULL ||
strcasecmp(options.chroot_directory, "none") == 0) {
#ifdef WINDOWS
char *resolved_shell;
#endif
char *shell = xstrdup((pw->pw_shell[0] == '\0') ?
_PATH_BSHELL : pw->pw_shell); /* empty = /bin/sh */

#ifdef WINDOWS
if ((resolved_shell = resolve_configured_user_path(shell,
pw->pw_dir, 1)) == NULL) {
logit("User %.100s not allowed because shell %.100s "
"could not be resolved to a fully qualified path",
pw->pw_name, shell);
free(shell);
return 0;
}
free(shell);
shell = resolved_shell;
#endif

if (stat(shell, &st) == -1) {
logit("User %.100s not allowed because shell %.100s "
"does not exist", pw->pw_name, shell);
Expand Down Expand Up @@ -570,8 +586,26 @@ getpwnamallow(struct ssh *ssh, const char *user)
auth_close(as);
#endif
#endif
if (pw != NULL)
return (pwcopy(pw));
if (pw != NULL) {
#ifdef WINDOWS
char *resolved_shell;
#endif
pw = pwcopy(pw);
#ifdef WINDOWS
if ((resolved_shell = resolve_configured_user_path(pw->pw_shell,
pw->pw_dir, 1)) == NULL) {
free(pw->pw_name);
free(pw->pw_passwd);
free(pw->pw_dir);
free(pw->pw_shell);
free(pw);
return (NULL);
}
free(pw->pw_shell);
pw->pw_shell = resolved_shell;
#endif
return (pw);
}
return (NULL);
}

Expand Down
192 changes: 191 additions & 1 deletion contrib/win32/win32compat/misc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1417,6 +1417,197 @@ is_absolute_path(const char *path)
return retVal;
}

static int
is_fully_qualified_path(const char *path)
{
char *slash;

if (path == NULL || *path == '\0')
return 0;
if (*path == '\"' || *path == '\'')
path++;

if (__isascii(path[0]) && isalpha(path[0]) && path[1] == ':' &&
(path[2] == '\\' || path[2] == '/'))
return 1;

if (path[0] != '\\' || path[1] != '\\')
return 0;

if (_strnicmp(path, "\\\\?\\UNC\\", 8) == 0)
path += 8;
else if (_strnicmp(path, "\\\\?\\", 4) == 0)
return __isascii(path[4]) && isalpha(path[4]) &&
path[5] == ':' && (path[6] == '\\' || path[6] == '/');
else if (_strnicmp(path, "\\\\.\\", 4) == 0)
return 0;
else
path += 2;

if (*path == '\0' || *path == '\\' || *path == '/')
return 0;
slash = strpbrk(path, "\\/");
if (slash == NULL || slash == path || slash[1] == '\0' ||
slash[1] == '\\' || slash[1] == '/')
return 0;

return 1;
}

static int
append_path_segment(char **buffer, size_t *capacity, size_t *length,
const char *segment, size_t segment_len)
{
char *tmp;
size_t new_capacity;

if (*length + segment_len + 1 > *capacity) {
new_capacity = *capacity == 0 ? 128 : *capacity;
while (*length + segment_len + 1 > new_capacity)
new_capacity *= 2;
if ((tmp = realloc(*buffer, new_capacity)) == NULL) {
errno = ENOMEM;
return -1;
}
*buffer = tmp;
*capacity = new_capacity;
}

memcpy(*buffer + *length, segment, segment_len);
*length += segment_len;
(*buffer)[*length] = '\0';
return 0;
}

static int
append_profile_path(char **buffer, size_t *capacity, size_t *length,
const char *profile_path, const char *suffix)
{
if (profile_path == NULL || *profile_path == '\0') {
errno = EINVAL;
return -1;
}
if (append_path_segment(buffer, capacity, length, profile_path,
strlen(profile_path)) != 0)
return -1;
if (suffix != NULL && append_path_segment(buffer, capacity, length,
suffix, strlen(suffix)) != 0)
return -1;
return 0;
}

static int
append_configured_path_variable(char **buffer, size_t *capacity,
size_t *length, const char *name, size_t name_len,
const char *profile_path)
{
char *name_buffer = NULL, *value = NULL;
size_t value_len;
int ret = -1;

if (name_len == 11 && _strnicmp(name, "USERPROFILE", name_len) == 0)
return append_profile_path(buffer, capacity, length,
profile_path, NULL);
if (name_len == 12 && _strnicmp(name, "LOCALAPPDATA", name_len) == 0)
return append_profile_path(buffer, capacity, length,
profile_path, "\\AppData\\Local");
if (name_len == 7 && _strnicmp(name, "APPDATA", name_len) == 0)
return append_profile_path(buffer, capacity, length,
profile_path, "\\AppData\\Roaming");

if ((name_buffer = calloc(name_len + 1, 1)) == NULL) {
errno = ENOMEM;
return -1;
}
memcpy(name_buffer, name, name_len);

if (_dupenv_s(&value, &value_len, name_buffer) != 0 ||
value == NULL) {
errno = EINVAL;
goto cleanup;
}
ret = append_path_segment(buffer, capacity, length, value,
strlen(value));

cleanup:
free(name_buffer);
free(value);
return ret;
}

static char *
expand_configured_path_variables(const char *path, const char *profile_path)
{
char *buffer = NULL, *end;
size_t capacity = 0, length = 0;

for (; *path != '\0'; path++) {
if (*path != '%') {
if (append_path_segment(&buffer, &capacity, &length,
path, 1) != 0)
goto cleanup;
continue;
}
if (path[1] == '%') {
if (append_path_segment(&buffer, &capacity, &length,
"%", 1) != 0)
goto cleanup;
path++;
continue;
}
if ((end = strchr(path + 1, '%')) == NULL) {
if (append_path_segment(&buffer, &capacity, &length,
path, 1) != 0)
goto cleanup;
continue;
}
if (end == path + 1) {
if (append_path_segment(&buffer, &capacity, &length,
"%", 1) != 0)
goto cleanup;
path = end;
continue;
}
if (append_configured_path_variable(&buffer, &capacity,
&length, path + 1, end - path - 1, profile_path) != 0)
goto cleanup;
path = end;
}

if (buffer == NULL && append_path_segment(&buffer, &capacity,
&length, "", 0) != 0)
goto cleanup;
return buffer;

cleanup:
free(buffer);
return NULL;
}

char *
resolve_configured_user_path(const char *path, const char *profile_path,
int require_absolute)
{
char *ret = NULL;

if (path == NULL || *path == '\0') {
errno = EINVAL;
return NULL;
}

if ((ret = expand_configured_path_variables(path, profile_path)) == NULL)
return NULL;
convertToBackslash(ret);

if (require_absolute && !is_fully_qualified_path(ret)) {
errno = EINVAL;
free(ret);
return NULL;
}

return ret;
}

/* return -1 - in case of failure, 0 - success */
int
create_directory_withsddl(wchar_t *path_w, wchar_t *sddl_w, BOOL check_permissions)
Expand Down Expand Up @@ -2148,4 +2339,3 @@ strrstr(const char *inStr, const char *pattern)

return last;
}

1 change: 1 addition & 0 deletions contrib/win32/win32compat/misc_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ HANDLE get_user_token(const char* user, int impersonation);
int load_user_profile(HANDLE user_token, char* user);
int create_directory_withsddl(wchar_t *path, wchar_t *sddl, BOOL check_permissions);
int is_absolute_path(const char *);
char * resolve_configured_user_path(const char *, const char *, int);
int file_in_chroot_jail(HANDLE);
int file_in_chroot_jail_helper(wchar_t*);
PSID lookup_sid(const wchar_t* name_utf16, PSID psid, DWORD * psid_len);
Expand Down
62 changes: 62 additions & 0 deletions regress/unittests/win32compat/miscellaneous_tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <misc_internal.h>
#include <sys/statvfs.h>
#include <unistd.h>
#include <Windows.h>

#include "../test_helper/test_helper.h"
#include "tests.h"
Expand Down Expand Up @@ -420,6 +421,66 @@ test_build_commandline_string()
TEST_DONE();
}

void
test_resolve_configured_user_path()
{
char *out, *old_test_root = NULL;
size_t old_test_root_len = 0;

TEST_START("configured path expansion");
_dupenv_s(&old_test_root, &old_test_root_len, "OPENSSH_TEST_ROOT");
SetEnvironmentVariableA("OPENSSH_TEST_ROOT", "C:\\OpenSSHTest");
out = resolve_configured_user_path("%OPENSSH_TEST_ROOT%\\pwsh.exe",
NULL, 1);
ASSERT_STRING_EQ(out, "C:\\OpenSSHTest\\pwsh.exe");
free(out);
SetEnvironmentVariableA("OPENSSH_TEST_ROOT", old_test_root);
free(old_test_root);
TEST_DONE();

TEST_START("configured user profile path expansion");
out = resolve_configured_user_path(
"%LOCALAPPDATA%\\Microsoft\\WindowsApps\\pwsh.exe",
"C:\\Users\\TestUser", 1);
ASSERT_STRING_EQ(out,
"C:\\Users\\TestUser\\AppData\\Local\\Microsoft\\WindowsApps\\pwsh.exe");
free(out);
TEST_DONE();

TEST_START("configured path requires fully qualified result");
out = resolve_configured_user_path("pwsh.exe", NULL, 1);
ASSERT_PTR_EQ(out, NULL);
ASSERT_INT_EQ(errno, EINVAL);
TEST_DONE();

TEST_START("configured path rejects rooted drive-relative result");
out = resolve_configured_user_path("\\Windows\\System32\\cmd.exe",
NULL, 1);
ASSERT_PTR_EQ(out, NULL);
ASSERT_INT_EQ(errno, EINVAL);
TEST_DONE();

TEST_START("configured path preserves explicit relative subsystem");
out = resolve_configured_user_path("sftp-server.exe", NULL, 0);
ASSERT_STRING_EQ(out, "sftp-server.exe");
free(out);
TEST_DONE();

TEST_START("configured path permits literal percent");
out = resolve_configured_user_path("C:\\OpenSSH%%Test\\pwsh.exe",
NULL, 1);
ASSERT_STRING_EQ(out, "C:\\OpenSSH%Test\\pwsh.exe");
free(out);
TEST_DONE();

TEST_START("configured path rejects unresolved environment");
out = resolve_configured_user_path("%OPENSSH_MISSING_VAR%\\pwsh.exe",
NULL, 1);
ASSERT_PTR_EQ(out, NULL);
ASSERT_INT_EQ(errno, EINVAL);
TEST_DONE();
}

void
miscellaneous_tests()
{
Expand All @@ -432,4 +493,5 @@ miscellaneous_tests()
test_chroot();
test_build_exec_command();
test_build_commandline_string();
test_resolve_configured_user_path();
}
Loading