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
7 changes: 6 additions & 1 deletion contrib/win32/openssh/OpenSSHTestHelper.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ $PubKeyUser = "sshtest_pubkeyuser"
$PasswdUser = "sshtest_passwduser"
$AdminUser = "sshtest_adminuser"
$NonAdminUser = "sshtest_nonadminuser"
$SshdUser = "sshd_user"
$OpenSSHTestAccountsPassword = "Bulldog_123456"
$OpenSSHTestAccounts = $Script:SSOUser, $Script:PubKeyUser, $Script:PasswdUser, $Script:AdminUser, $Script:NonAdminUser
$OpenSSHTestAccounts = $Script:SSOUser, $Script:PubKeyUser, $Script:PasswdUser, $Script:AdminUser, $Script:NonAdminUser, $Script:SshdUser
$SSHDTestSvcName = "sshdTestSvc"

$Script:TestDataPath = "$env:SystemDrive\OpenSSHTests"
Expand Down Expand Up @@ -69,6 +70,7 @@ function Set-OpenSSHTestEnvironment
$Global:OpenSSHTestInfo.Add("PasswdUser", $PasswdUser) # test user to be used for password auth
$Global:OpenSSHTestInfo.Add("AdminUser", $AdminUser) # test user to be used for admin logging tests
$Global:OpenSSHTestInfo.Add("NonAdminUser", $NonAdminUser) # test user to be used for non-admin logging tests
$Global:OpenSSHTestInfo.Add("SshdUser", $SshdUser) # non-admin local user used by UserEnvironment tests
$Global:OpenSSHTestInfo.Add("TestAccountPW", $OpenSSHTestAccountsPassword) # common password for all test accounts
$Global:OpenSSHTestInfo.Add("DebugMode", $DebugMode.IsPresent) # run openssh E2E in debug mode
$Global:OpenSSHTestInfo.Add("DelayTime", 3) # delay between stoppig sshd service and trying to access log files
Expand Down Expand Up @@ -225,6 +227,9 @@ WARNING: Following changes will be made to OpenSSH configuration
$NonAdminUserProfile = Get-LocalUserProfile -User $NonAdminUser
$Global:OpenSSHTestInfo.Add("NonAdminUserProfile", $NonAdminUserProfile)

$SshdUserProfile = Get-LocalUserProfile -User $SshdUser
$Global:OpenSSHTestInfo.Add("SshdUserProfile", $SshdUserProfile)

#make $AdminUser admin
net localgroup Administrators $AdminUser /add

Expand Down
13 changes: 9 additions & 4 deletions contrib/win32/win32compat/spawn-ext.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include "inc\unistd.h"
#include "debug.h"

int posix_spawn_internal(pid_t *pidp, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[], HANDLE user_token, BOOLEAN prepend_module_path);
int posix_spawn_internal(pid_t *pidp, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[], HANDLE user_token, BOOLEAN prepend_module_path, BOOLEAN load_user_env);

int
__posix_spawn_asuser(pid_t *pidp, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[], char* user)
Expand All @@ -25,10 +25,15 @@ __posix_spawn_asuser(pid_t *pidp, const char *path, const posix_spawn_file_actio
errno = EOTHER;
return -1;
}
if (strcmp(user, "sshd"))
/*
* This identity check decides whether to load both the user's profile and
* environment block.
*/
BOOLEAN is_privsep = (strcmp(user, "sshd") == 0);
if (!is_privsep)
load_user_profile(user_token, user);
r = posix_spawn_internal(pidp, path, file_actions, attrp, argv, envp, user_token, TRUE);

r = posix_spawn_internal(pidp, path, file_actions, attrp, argv, envp, user_token, TRUE, !is_privsep);
CloseHandle(user_token);
return r;
}
66 changes: 12 additions & 54 deletions contrib/win32/win32compat/w32fd.c
Original file line number Diff line number Diff line change
Expand Up @@ -1055,45 +1055,6 @@ int fork()
}
char * build_commandline_string(const char* cmd, char *const argv[], BOOLEAN prepend_module_path);

wchar_t*
get_username_from_token(HANDLE as_user)
{
wchar_t* user_name = NULL;
SID_NAME_USE usage;
DWORD count = 0;
GetTokenInformation(as_user, TokenUser, NULL, 0, &count);
if (count) {
void* buffer = malloc(count);
if (buffer) {
if (GetTokenInformation(as_user, TokenUser, buffer, count, &count)) {
TOKEN_USER* owner = (TOKEN_USER*)buffer;
DWORD name_length = 0;
DWORD domain_length = 0;
LookupAccountSidW(NULL, owner->User.Sid, NULL, &name_length, NULL, &domain_length, &usage); /* Figure out the length of the name. */
if (name_length) {
wchar_t* domain_name = malloc(domain_length * sizeof(wchar_t));
if (domain_name) {
user_name = malloc(name_length * sizeof(wchar_t));
if (user_name) {
memset(user_name, 0, name_length * sizeof(wchar_t));
memset(domain_name, 0, domain_length * sizeof(wchar_t));
BOOL success = LookupAccountSidW(NULL, owner->User.Sid, user_name, &name_length, domain_name, &domain_length, &usage);
if (!success) /* Silently return an empty string if unsuccessful. */
{
free(user_name);
user_name = NULL;
}
}
free(domain_name);
}
}
}
free(buffer);
}
}
return user_name;
}

/*
* spawn a child process
* - specified by cmd with agruments argv
Expand All @@ -1102,14 +1063,15 @@ get_username_from_token(HANDLE as_user)
* spawned child will run as as_user if its not NULL
*/
static int
spawn_child_internal(const char* cmd, char *const argv[], HANDLE in, HANDLE out, HANDLE err, unsigned long flags, HANDLE as_user, BOOLEAN prepend_module_path)
spawn_child_internal(const char* cmd, char *const argv[], HANDLE in, HANDLE out, HANDLE err, unsigned long flags, HANDLE as_user, BOOLEAN prepend_module_path, BOOLEAN load_user_env)
{
PROCESS_INFORMATION pi;
STARTUPINFOW si;
BOOL b;
BOOL b = FALSE;
char *cmdline;
wchar_t * cmdline_utf16 = NULL;
int ret = -1;

if ((cmdline = build_commandline_string(cmd, argv, prepend_module_path)) == NULL) {
errno = ENOMEM;
goto cleanup;
Expand All @@ -1125,7 +1087,7 @@ spawn_child_internal(const char* cmd, char *const argv[], HANDLE in, HANDLE out,
si.hStdOutput = out;
si.hStdError = err;
si.dwFlags = STARTF_USESTDHANDLES;

if (strstr(cmd, "sshd-session.exe") || strstr(cmd, "sshd-auth.exe")) {
flags |= DETACHED_PROCESS;
}
Expand All @@ -1146,12 +1108,8 @@ spawn_child_internal(const char* cmd, char *const argv[], HANDLE in, HANDLE out,
if (as_user) {
debug3("spawning %ls as user", t);
LPVOID lpEnvironment = NULL;
wchar_t* as_user_name = get_username_from_token(as_user);
if (as_user_name) {
if (wcsncmp(L"sshd", as_user_name, wcslen(L"sshd")) != 0) { /* Ignore any names that begin with the service name `sshd`. */
b = CreateEnvironmentBlock(&lpEnvironment, as_user, TRUE); /* Load a user environment block inheriting the current context, thereby passing session state. */
}
free(as_user_name);
if (load_user_env) {
CreateEnvironmentBlock(&lpEnvironment, as_user, TRUE);
}
if (lpEnvironment) { /* Pass the user environment block to the new process. */
b = CreateProcessAsUserW(as_user, NULL, t, NULL, NULL, TRUE, flags | CREATE_UNICODE_ENVIRONMENT, lpEnvironment, NULL, &si, &pi);
Expand Down Expand Up @@ -1311,7 +1269,7 @@ fd_decode_state(char* enc_buf)
}

int
posix_spawn_internal(pid_t *pidp, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[], HANDLE user_token, BOOLEAN prepend_module_path)
posix_spawn_internal(pid_t *pidp, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[], HANDLE user_token, BOOLEAN prepend_module_path, BOOLEAN load_user_env)
{
int i, ret = -1;
int sc_flags = 0;
Expand Down Expand Up @@ -1347,7 +1305,7 @@ posix_spawn_internal(pid_t *pidp, const char *path, const posix_spawn_file_actio

if (_putenv_s(POSIX_FD_STATE, fd_info) != 0)
goto cleanup;
i = spawn_child_internal(path, argv + 1, stdio_handles[STDIN_FILENO], stdio_handles[STDOUT_FILENO], stdio_handles[STDERR_FILENO], sc_flags, user_token, prepend_module_path);
i = spawn_child_internal(path, argv + 1, stdio_handles[STDIN_FILENO], stdio_handles[STDOUT_FILENO], stdio_handles[STDERR_FILENO], sc_flags, user_token, prepend_module_path, load_user_env);
if (i == -1)
goto cleanup;
if (pidp)
Expand Down Expand Up @@ -1380,23 +1338,23 @@ posix_spawn_internal(pid_t *pidp, const char *path, const posix_spawn_file_actio
int
posix_spawn(pid_t *pidp, const char *path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[])
{
return posix_spawn_internal(pidp, path, file_actions, attrp, argv, envp, NULL, TRUE);
return posix_spawn_internal(pidp, path, file_actions, attrp, argv, envp, NULL, TRUE, TRUE);
}

int
posix_spawnp(pid_t *pidp, const char *file, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[])
{
return posix_spawn_internal(pidp, file, file_actions, attrp, argv, envp, NULL, FALSE);
return posix_spawn_internal(pidp, file, file_actions, attrp, argv, envp, NULL, FALSE, TRUE);
}

int
posix_spawn_as_user(pid_t *pidp, const char *file, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[], HANDLE user_token)
{
return posix_spawn_internal(pidp, file, file_actions, attrp, argv, envp, user_token, TRUE);
return posix_spawn_internal(pidp, file, file_actions, attrp, argv, envp, user_token, TRUE, TRUE);
}

int
posix_spawnp_as_user(pid_t *pidp, const char *file, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const argv[], char *const envp[], HANDLE user_token)
{
return posix_spawn_internal(pidp, file, file_actions, attrp, argv, envp, user_token, FALSE);
return posix_spawn_internal(pidp, file, file_actions, attrp, argv, envp, user_token, FALSE, TRUE);
}
131 changes: 131 additions & 0 deletions regress/pesterTests/UserEnvironment.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
If ($PSVersiontable.PSVersion.Major -le 2) {$PSScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path}
Import-Module $PSScriptRoot\CommonUtils.psm1 -Force
Import-Module OpenSSHUtils -Force

$tC = 1
$tI = 0
$suite = "userenvironment"

Describe "E2E scenarios for user environment block" -Tags "CI" {
BeforeAll {
if ($OpenSSHTestInfo -eq $null) {
throw "`$OpenSSHTestInfo is null. Please run Set-OpenSSHTestEnvironment to set test environments."
}

$server = $OpenSSHTestInfo["Target"]
$port = $OpenSSHTestInfo["Port"]

$testDir = Join-Path $OpenSSHTestInfo["TestDataPath"] $suite
if (-not (Test-Path $testDir)) {
$null = New-Item $testDir -ItemType directory -Force -ErrorAction SilentlyContinue
}

$script:envTestUser = $OpenSSHTestInfo["SshdUser"]
$script:envTestProfile = $OpenSSHTestInfo["SshdUserProfile"]

$keypassphrase = "testpassword"
$script:envTestKey = Join-Path $testDir "sshd_user_envtest_ed25519"
Remove-Item -Path "$($script:envTestKey)*" -Force -ErrorAction SilentlyContinue
ssh-keygen.exe -q -t ed25519 -f $script:envTestKey -N $keypassphrase
$envTestSshDir = Join-Path $script:envTestProfile .ssh
if (-not (Test-Path $envTestSshDir -PathType Container)) {
New-Item $envTestSshDir -ItemType Directory -Force -ErrorAction Stop | Out-Null
}
$script:envTestAuthKeys = Join-Path $envTestSshDir authorized_keys
Copy-Item "$($script:envTestKey).pub" $script:envTestAuthKeys -Force
Repair-AuthorizedKeyPermission -FilePath $script:envTestAuthKeys -confirm:$false
Add-PasswordSetting -Pass $keypassphrase
}

AfterAll {
Remove-PasswordSetting
if ($script:envTestKey) {
Remove-Item -Path "$($script:envTestKey)*" -Force -ErrorAction SilentlyContinue
}
if ($script:envTestAuthKeys -and (Test-Path $script:envTestAuthKeys)) {
Remove-Item $script:envTestAuthKeys -Force -ErrorAction SilentlyContinue
}
}

AfterEach { $tI++ }
Comment thread
tgauth marked this conversation as resolved.

Context "$tC - User environment variables" {
BeforeAll { $tI = 1 }
AfterAll { $tC++ }

It "$tC.$tI - USERNAME matches the connecting user" {
$o = ssh -p $port -i $script:envTestKey "$($script:envTestUser)@$server" echo %USERNAME%
Comment thread
tgauth marked this conversation as resolved.
"$o".Trim() | Should Be $script:envTestUser
}

It "$tC.$tI - USERPROFILE points to the connecting user's profile" {
$o = ssh -p $port -i $script:envTestKey "$($script:envTestUser)@$server" echo %USERPROFILE%
$remote = "$o".Trim()
$remote | Should Not BeNullOrEmpty
$remote | Should Not Match 'system32\\config\\systemprofile'
# Profile leaf is normally the user name, but Windows can suffix
# it (e.g. sshd_user.DESKTOP-XYZ.001) when the profile dir was
# recreated, so just check it starts with the user name.
($remote -split '\\')[-1] | Should Match ("^" + [regex]::Escape($script:envTestUser))
}

It "$tC.$tI - HOMEDRIVE and HOMEPATH resolve to the user's profile" {
$hd = "$(ssh -p $port -i $script:envTestKey "$($script:envTestUser)@$server" echo %HOMEDRIVE%)".Trim()
$hp = "$(ssh -p $port -i $script:envTestKey "$($script:envTestUser)@$server" echo %HOMEPATH%)".Trim()
$up = "$(ssh -p $port -i $script:envTestKey "$($script:envTestUser)@$server" echo %USERPROFILE%)".Trim()
$hp | Should Not Match 'system32\\config\\systemprofile'
$hp | Should Not Match '^\\Windows'
$hp | Should Match ("^\\Users\\" + [regex]::Escape($script:envTestUser))
$hd | Should Be $env:SystemDrive
($hd + $hp) | Should Be $up
}

It "$tC.$tI - APPDATA is populated for user" {
$ad = "$(ssh -p $port -i $script:envTestKey "$($script:envTestUser)@$server" echo %APPDATA%)".Trim()
$ad | Should Not Match 'system32\\config\\systemprofile'
$ad | Should Match 'AppData\\Roaming$'
$ad | Should Match ([regex]::Escape($script:envTestUser))
}

It "$tC.$tI - LOCALAPPDATA is populated for user" {
$la = "$(ssh -p $port -i $script:envTestKey "$($script:envTestUser)@$server" echo %LOCALAPPDATA%)".Trim()
$la | Should Not Match 'system32\\config\\systemprofile'
$la | Should Match 'AppData\\Local$'
$la | Should Match ([regex]::Escape($script:envTestUser))
}

It "$tC.$tI - USERDOMAIN equals the local computer name" {
$o = ssh -p $port -i $script:envTestKey "$($script:envTestUser)@$server" echo %USERDOMAIN%
$remote = "$o".Trim()
$remote | Should Not Be 'WORKGROUP'
$remote | Should Be $env:COMPUTERNAME
}
}

Context "$tC - PATH variable" {
BeforeAll {
$tI = 1
$script:pathMarker = "C:\sshtestmarker_$([guid]::NewGuid().ToString('N'))"
ssh -p $port -i $script:envTestKey "$($script:envTestUser)@$server" `
reg add HKCU\Environment /v Path /t REG_EXPAND_SZ /d $($script:pathMarker) /f | Out-Null
}
AfterAll {
ssh -p $port -i $script:envTestKey "$($script:envTestUser)@$server" `
reg delete HKCU\Environment /v Path /f | Out-Null
$tC++
}

It "$tC.$tI - contains both system and user entries" {
$o = ssh -p $port -i $script:envTestKey "$($script:envTestUser)@$server" echo %PATH%
$remote = "$o".Trim()
$segments = $remote -split ';'
$sysMatches = @($segments | Where-Object { $_ -match '[Ss]ystem32' })
$markerMatches = @($segments | Where-Object { $_ -eq $script:pathMarker })
$sysMatches.Count | Should BeGreaterThan 0
$markerMatches.Count | Should BeGreaterThan 0
$sysIndex = [array]::IndexOf($segments, $sysMatches[0])
$markerIndex = [array]::IndexOf($segments, $script:pathMarker)
$sysIndex | Should BeLessThan $markerIndex
}
}
}