From a49bdce6665bdf25e53160cee3de5c4fa6529b67 Mon Sep 17 00:00:00 2001 From: bagajjal Date: Mon, 9 Dec 2019 15:12:46 -0800 Subject: [PATCH] Port Unix bash tests (#410) Leverage upstream test infrastructure to run against windows openssh binaries. --- contrib/win32/openssh/AppveyorHelper.psm1 | 36 +- contrib/win32/openssh/OpenSSHTestHelper.psm1 | 44 ++- .../win32/openssh/bash_script_iterator.ps1 | 276 -------------- contrib/win32/openssh/bash_tests_iterator.ps1 | 286 +++++++++++++++ contrib/win32/win32compat/misc.c | 339 ++++++++++-------- contrib/win32/win32compat/termio.c | 5 + contrib/win32/win32compat/w32fd.c | 4 +- contrib/win32/win32compat/win32_groupaccess.c | 11 +- match.c | 10 +- openbsd-compat/timingsafe_bcmp.c | 8 +- regress/cfgmatch.sh | 9 +- regress/cfgmatchlisten.sh | 18 +- regress/hostkey-rotate.sh | 9 +- regress/sftp-perm.sh | 10 +- regress/sshcfgparse.sh | 30 ++ regress/test-exec.sh | 23 +- .../win32compat/miscellaneous_tests.c | 2 +- sftp-server.c | 5 + 18 files changed, 649 insertions(+), 476 deletions(-) delete mode 100644 contrib/win32/openssh/bash_script_iterator.ps1 create mode 100644 contrib/win32/openssh/bash_tests_iterator.ps1 diff --git a/contrib/win32/openssh/AppveyorHelper.psm1 b/contrib/win32/openssh/AppveyorHelper.psm1 index d4e2f7b3a..f5a9e2469 100644 --- a/contrib/win32/openssh/AppveyorHelper.psm1 +++ b/contrib/win32/openssh/AppveyorHelper.psm1 @@ -277,6 +277,12 @@ function Publish-Artifact Add-Artifact -artifacts $artifacts -FileToAdd $Global:OpenSSHTestInfo["UninstallTestResultsFile"] Add-Artifact -artifacts $artifacts -FileToAdd $Global:OpenSSHTestInfo["TestSetupLogFile"] } + + if ($Global:bash_tests_summary) + { + Add-Artifact -artifacts $artifacts -FileToAdd $Global:bash_tests_summary["BashTestSummaryFile"] + Add-Artifact -artifacts $artifacts -FileToAdd $Global:bash_tests_summary["BashTestLogFile"] + } foreach ($artifact in $artifacts) { @@ -324,7 +330,7 @@ function Invoke-OpenSSHTests else { Write-Host "All Unit tests passed!" - Write-BuildMessage -Message "All Unit tests passed!" -Category Information + Write-BuildMessage -Message "All Unit tests passed!" -Category Information } # Run all E2E tests. @@ -339,7 +345,7 @@ function Invoke-OpenSSHTests return } $xml = [xml](Get-Content $OpenSSHTestInfo["E2ETestResultsFile"] | out-string) - if ([int]$xml.'test-results'.failures -gt 0) + if ([int]$xml.'test-results'.failures -gt 0) { $errorMessage = "$($xml.'test-results'.failures) tests in regress\pesterTests failed. Detail test log is at $($OpenSSHTestInfo["E2ETestResultsFile"])." Write-Warning $errorMessage @@ -347,7 +353,31 @@ function Invoke-OpenSSHTests Set-BuildVariable TestPassed False Write-Warning "Stop running further tests!" return - } + } + + # Run UNIX bash tests. + Invoke-OpenSSHBashTests + if (-not $Global:bash_tests_summary) + { + $errorMessage = "Failed to start OpenSSH bash tests" + Write-Warning $errorMessage + Write-BuildMessage -Message $errorMessage -Category Error + Set-BuildVariable TestPassed False + Write-Warning "Stop running further tests!" + return + } + + if ($Global:bash_tests_summary["TotalBashTestsFailed"] -ne 0) + { + $total_bash_failed_tests = $Global:bash_tests_summary["TotalBashTestsFailed"] + $total_bash_tests = $Global:bash_tests_summary["TotalBashTests"] + $errorMessage = "At least one of the bash tests failed. [$total_bash_failed_tests of $total_bash_tests]" + Write-Warning $errorMessage + Write-BuildMessage -Message $errorMessage -Category Error + Set-BuildVariable TestPassed False + Write-Warning "Stop running further tests!" + return + } Invoke-OpenSSHUninstallTest if (($OpenSSHTestInfo -eq $null) -or (-not (Test-Path $OpenSSHTestInfo["UninstallTestResultsFile"]))) diff --git a/contrib/win32/openssh/OpenSSHTestHelper.psm1 b/contrib/win32/openssh/OpenSSHTestHelper.psm1 index 2234b2319..ab9ca4a69 100644 --- a/contrib/win32/openssh/OpenSSHTestHelper.psm1 +++ b/contrib/win32/openssh/OpenSSHTestHelper.psm1 @@ -41,7 +41,7 @@ function Set-OpenSSHTestEnvironment param ( [string] $OpenSSHBinPath, - [string] $TestDataPath = "$env:SystemDrive\OpenSSHTests", + [string] $TestDataPath = "$env:SystemDrive\OpenSSHTests", [Switch] $DebugMode, [Switch] $NoAppVerifier, [Switch] $PostmortemDebugging, @@ -68,7 +68,6 @@ function Set-OpenSSHTestEnvironment $Global:OpenSSHTestInfo.Add("TestAccountPW", $OpenSSHTestAccountsPassword) # common password for all test accounts $Global:OpenSSHTestInfo.Add("DebugMode", $DebugMode.IsPresent) # run openssh E2E in debug mode - $Script:EnableAppVerifier = -not ($NoAppVerifier.IsPresent) if($Script:WindowsInBox = $true) { @@ -100,8 +99,7 @@ WARNING: Following changes will be made to OpenSSH configuration return } - Install-OpenSSHTestDependencies - + Install-OpenSSHTestDependencies ##### START: install sshd test service #delete service if exists @@ -164,15 +162,13 @@ WARNING: Following changes will be made to OpenSSH configuration } Start-Service ssh-agent - - #Prepare user config - known_hosts and ssh_config $dotSshDirectoryPath = Join-Path $home .ssh if(-not (Test-Path $dotSshDirectoryPath -PathType Container)) { New-Item -ItemType Directory -Path $dotSshDirectoryPath -Force -ErrorAction SilentlyContinue | out-null } - + $knowHostsFilePath = Join-Path $dotSshDirectoryPath known_hosts if (-not (Test-Path $knowHostsFilePath -PathType Leaf)) { Copy-Item (Join-Path $Script:E2ETestDataDirectory known_hosts) $knowHostsFilePath -Force @@ -671,6 +667,38 @@ function Invoke-OpenSSHE2ETest Pop-Location } +<# + .Synopsis + Run UNIX bash tests using CYGWIN. +#> +function Invoke-OpenSSHBashTests +{ + [string]$bashPath = [string]::Empty + # Check for cygwin + if (Test-Path $env:SystemDrive\cygwin\bin\sh.exe) { + $bashPath = "$env:SystemDrive\cygwin\bin\sh.exe" + } elseif (Test-Path $env:SystemDrive\cygwin64\bin\sh.exe) { + $bashPath = "$env:SystemDrive\cygwin64\bin\sh.exe" + } elseif (Test-Path $env:SystemDrive\tools\cygwin\bin\sh.exe) { + $bashPath = "$env:SystemDrive\tools\cygwin\bin\sh.exe" + } else { + # Install cygwin + Write-Host "Installing cygwin using chocolatey to $env:SystemDrive\cygwin folder" + choco install cygwin -y --params "/InstallDir:$env:SystemDrive\cygwin\ /NoStartMenu" + + if (Test-Path $env:SystemDrive\cygwin\bin\sh.exe) { + $bashPath = "$env:SystemDrive\cygwin\bin\sh.exe" + } else { + Write-Error "Failed to install cygwin to $env:SystemDrive\cygwin folder" -ErrorAction Stop + return + } + } + + $bashTestDirectory = Join-Path $repositoryRoot.FullName -ChildPath "regress" + + &"$PSScriptRoot\bash_tests_iterator.ps1" -OpenSSHBinPath $Script:OpenSSHBinPath -BashTestsPath $bashTestDirectory -ShellPath $bashPath -ArtifactsDirectoryPath $bashTestDirectory +} + <# .Synopsis Run openssh unit tests. @@ -759,4 +787,4 @@ function Write-Log } } -Export-ModuleMember -Function Set-BasicTestInfo, Set-OpenSSHTestEnvironment, Clear-OpenSSHTestEnvironment, Invoke-OpenSSHSetupTest, Invoke-OpenSSHUnitTest, Invoke-OpenSSHE2ETest, Invoke-OpenSSHUninstallTest \ No newline at end of file +Export-ModuleMember -Function Set-BasicTestInfo, Set-OpenSSHTestEnvironment, Clear-OpenSSHTestEnvironment, Invoke-OpenSSHSetupTest, Invoke-OpenSSHUnitTest, Invoke-OpenSSHE2ETest, Invoke-OpenSSHUninstallTest, Invoke-OpenSSHBashTests \ No newline at end of file diff --git a/contrib/win32/openssh/bash_script_iterator.ps1 b/contrib/win32/openssh/bash_script_iterator.ps1 deleted file mode 100644 index fb915eb63..000000000 --- a/contrib/win32/openssh/bash_script_iterator.ps1 +++ /dev/null @@ -1,276 +0,0 @@ -param ( - # Path to openssh binaries - [Parameter(Mandatory=$true)] [string] $OpenSSHBinPath, - # Path of regress folder which has all the bash testcases. - [Parameter(Mandatory=$true)] [string] $BashTestsPath, - # Path to CYGWIN / WSL. - [Parameter(Mandatory=$true)] [string] $ShellPath, - # Individual bash test file (Ex - connect.sh) - [Parameter(Mandatory=$false)] [string[]] $TestFilePath, - [Parameter(Mandatory=$false)] [string] $ArtifactsPath=".", - [switch] $SkipCleanup, - [switch] $SkipInstallSSHD -) - -# Resolve the relative paths -$OpenSSHBinPath=resolve-path $OpenSSHBinPath -ErrorAction Stop | select -ExpandProperty Path -$BashTestsPath=resolve-path $BashTestsPath -ErrorAction Stop | select -ExpandProperty Path -$ShellPath=resolve-path $ShellPath -ErrorAction Stop | select -ExpandProperty Path -$ArtifactsPath=resolve-path $ArtifactsPath -ErrorAction Stop | select -ExpandProperty Path -if ($TestFilePath) { - $TestFilePath=resolve-path $TestFilePath -ErrorAction Stop | select -ExpandProperty Path - # convert to bash format - $TestFilePath=$TestFilePath -replace "\\","/" -} - -# Make sure config.h exists. It is used by bashstests (Ex - sftp-glob.sh, cfgparse.sh) -# first check in $BashTestsPath folder. If not then it's parent folder. If not then in the $OpenSSHBinPath -if(Test-Path "$BashTestsPath\config.h" -PathType Leaf) { - $configPath="$BashTestsPath\config.h" -} elseif(Test-Path "$BashTestsPath\..\config.h" -PathType Leaf) { - $configPath=resolve-path "$BashTestsPath\..\config.h" -ErrorAction Stop | select -ExpandProperty Path -} elseif(Test-Path "$OpenSSHBinPath\config.h" -PathType Leaf) { - $configPath="$OpenSSHBinPath\config.h" -} else { - Write-Error "Couldn't find config.h" - exit -} - -# store user directory -$user_pwd=pwd | select -ExpandProperty Path - -# If we are using a SKU with desired OpenSSH binaries then we can skip these steps. -if(!$SkipInstallSSHD) { - # Make sure install-sshd.ps1 exists. - if(!(Test-Path "$PSScriptRoot\install-sshd.ps1" -PathType Leaf)) { - Write-Error "$PSScriptRoot\install-sshd.ps1 doesn't exists" - exit - } - - # Make sure uninstall-sshd.ps1 exists. - if(!(Test-Path "$PSScriptRoot\uninstall-sshd.ps1" -PathType Leaf)) { - Write-Error "$PSScriptRoot\uninstall-sshd.ps1 doesn't exists" - exit - } - - #copy to binary folder and execute install-sshd.ps1 - Copy-Item $PSScriptRoot\install-sshd.ps1 -Force $OpenSSHBinPath - Copy-Item $PSScriptRoot\uninstall-sshd.ps1 -Force $OpenSSHBinPath - - # We need ssh-agent to be installed as service to run some bash tests. - & "$OpenSSHBinPath\install-sshd.ps1" -} - -try -{ - # set the default shell - $registryPath = "HKLM:\Software\OpenSSH" - $dfltShell = "DefaultShell" - # Fetch the user configured default shell. - $out=(Get-ItemProperty -Path $registryPath -Name $dfltShell -ErrorAction SilentlyContinue) - if($out) { - $user_default_shell = $out.$dfltShell - Write-Output "User configured default shell: $user_default_shell" - } - - if (!(Test-Path $registryPath)) { - # start and stop the sshd so that "HKLM:\Software\OpenSSH" registry path is created. - Start-Service sshd -ErrorAction Stop - Stop-Service sshd -ErrorAction SilentlyContinue - } - - Set-ItemProperty -Path $registryPath -Name $dfltShell -Value $ShellPath -Force - $out=(Get-ItemProperty -Path $registryPath -Name $dfltShell -ErrorAction SilentlyContinue) - if($out.$dfltShell -ne $ShellPath) { - Write-Output "Failed to set HKLM:\Software\OpenSSH\DefaultShell to $ShellPath" - exit - } - - # Prepend shell path to PATH. This is required to make the shell commands (like sleep, cat, etc) work properly. - $env:TEST_SHELL_PATH=$ShellPath -replace "\\","/" - $TEST_SHELL_DIR=split-path $ShellPath - if(!$env:path.StartsWith($TEST_SHELL_DIR, "CurrentCultureIgnoreCase")) - { - $env:path=$TEST_SHELL_DIR+";"+$env:path - } - - $BashTestsPath=$BashTestsPath -replace "\\","/" - Push-location $BashTestsPath - - # BUILDDIR: config.h location. - # BUILDDIR is used by bashstests (Ex - sftp-glob.sh, cfgparse.sh) - $BUILDDIR=resolve-path(split-path $configpath) | select -ExpandProperty Path - $tmp=&$ShellPath -c pwd - if ($tmp.StartsWith("/cygdrive/")) { - $shell_drv_fmt="/cygdrive/" # "/cygdrive/" - cygwin - $BUILDDIR=&$ShellPath -c "cygpath -u '$BUILDDIR'" - $OpenSSHBinPath_shell_fmt=&$ShellPath -c "cygpath -u '$OpenSSHBinPath'" - $BashTestsPath=&$ShellPath -c "cygpath -u '$BashTestsPath'" - } elseif ($tmp.StartsWith("/mnt/")) { - $shell_drv_fmt="/mnt/" # "/mnt/" - WSL bash - $BUILDDIR=&$ShellPath -c "wslpath -u '$BUILDDIR'" - $OpenSSHBinPath_shell_fmt=&$ShellPath -c "wslpath -u '$OpenSSHBinPath'" - $BashTestsPath=&$ShellPath -c "wslpath -u '$BashTestsPath'" - } - - #set the environment variables. - $env:ShellPath=$ShellPath - $env:SSH_TEST_ENVIRONMENT=1 - $env:TEST_SSH_TRACE="yes" - $env:TEST_SHELL="/bin/sh" - $env:TEST_SSH_PORT=22 - $env:TEST_SSH_SSH=$OpenSSHBinPath_shell_fmt+"/ssh.exe" - $env:TEST_SSH_SSHD=$OpenSSHBinPath_shell_fmt+"/sshd.exe" - $env:TEST_SSH_SSHAGENT=$OpenSSHBinPath_shell_fmt+"/ssh-agent.exe" - $env:TEST_SSH_SSHADD=$OpenSSHBinPath_shell_fmt+"/ssh-add.exe" - $env:TEST_SSH_SSHKEYGEN=$OpenSSHBinPath_shell_fmt+"/ssh-keygen.exe" - $env:TEST_SSH_SSHKEYSCAN=$OpenSSHBinPath_shell_fmt+"/ssh-keyscan.exe" - $env:TEST_SSH_SFTP=$OpenSSHBinPath_shell_fmt+"/sftp.exe" - $env:TEST_SSH_SFTPSERVER=$OpenSSHBinPath_shell_fmt+"/sftp-server.exe" - $env:TEST_SSH_SCP=$OpenSSHBinPath_shell_fmt+"/scp.exe" - $env:BUILDDIR=$BUILDDIR - $env:TEST_WINDOWS_SSH=1 - $user = &"$env:windir\system32\whoami.exe" - if($user.Contains($env:COMPUTERNAME.ToLower())) { - # for local accounts, skip COMPUTERNAME - $user = Split-Path $user -leaf - $env:TEST_SSH_USER=$user - } else { - # for domain user convert "domain\user" to "domain/user". - $user = $user -replace "\\","/" - $env:TEST_SSH_USER = $user - $env:TEST_SSH_USER_DOMAIN = Split-Path $user - } - - # output to terminal - Write-Output "USER: $env:TEST_SSH_USER" - Write-Output "DOMAIN: $env:TEST_SSH_USER_DOMAIN" - Write-Output "OpenSSHBinPath: $OpenSSHBinPath_shell_fmt" - Write-Output "BUILDDIR: $BUILDDIR" - Write-Output "BashTestsPath: $BashTestsPath" - - # remove, create the temp test directory - $temp_test_path="temp_test" - $null = Remove-Item -Recurse -Force $temp_test_path -ErrorAction SilentlyContinue - $null = New-Item -ItemType directory -Path $temp_test_path -ErrorAction Stop - - # remove the summary, output files. - $test_summary="$ArtifactsPath\\bashtest_summary.txt" - $test_output="$ArtifactsPath\\bashtest_output.txt" - $null = Remove-Item -Force $test_summary -ErrorAction SilentlyContinue - $null = Remove-Item -Force $test_output -ErrorAction SilentlyContinue - [int]$total_tests = 0 - [int]$total_tests_passed = 0 - [int]$total_tests_failed = 0 - [string]$failed_testcases = [string]::Empty - - # These are the known failed testcases. - # agent.sh, krl.sh - User Cert authentication fails - # key-options.sh - pty testcases are failing (bug in conpty. conpty fails to launch cygwin bash) - # integrity.sh - It's dependent on regress\modpipe.exe, test is complicated. Need to debug more - # authinfo.sh - spawned conpty inherits all the environment variables from sshd. - # forward-control.sh - Need to debug more. - [string]$known_failed_testcases = "agent.sh key-options.sh forward-control.sh integrity.sh krl.sh authinfo.sh" - [string]$known_failed_testcases_skipped = [string]::Empty - - $start_time = (Get-Date) - - if($TestFilePath) { - # User can specify individual test file path. - $all_tests=$TestFilePath - } else { - # picking the gawk.exe from bash folder. - # TODO - check if gawk.exe is present in WSL. - $all_tests=gawk.exe 'sub(/.*LTESTS=/,""""){f=1} f{print $1; if (!/\\\\/) exit}' Makefile - } - - foreach($test_case in $all_tests) { - if($TestFilePath) { - $TEST=$test_case - } else { - if(!$test_case.Contains(".sh")) { - $TEST=$BashTestsPath+"/"+$test_case+".sh" - } else { - $TEST=$BashTestsPath+"/"+$test_case - } - } - - $test_case_name = [System.IO.Path]::GetFileName($TEST) - if($known_failed_testcases.Contains($test_case_name)) - { - Write-Output "Skip the known failed test:$test_case_name" - $known_failed_testcases_skipped = $known_failed_testcases_skipped + "$test_case_name " - } - else - { - $msg="Executing $test_case_name " +[System.DateTime]::Now - Write-Output $msg - &$env:ShellPath -c "/usr/bin/sh $BashTestsPath/test-exec.sh $BashTestsPath/$temp_test_path $TEST" #>$null 2>&1 - if($?) - { - $msg="$test_case_name PASSED " +[System.DateTime]::Now - Write-Output $msg - $total_tests_passed++ - } - else - { - $msg="$test_case_name FAILED " +[System.DateTime]::Now - Write-Output $msg - $total_tests_failed++ - $failed_testcases=$failed_testcases + "$test_case_name " - } - } - $total_tests++ - } - - $end_time = (Get-Date) - - # Create artifacts - "Start time: $start_time" | Out-File -FilePath $test_summary -Append - "End time: $end_time" | Out-File -FilePath $test_summary -Append - "Total execution time: " + (NEW-TIMESPAN -Start $start_time -End $end_time).ToString("hh\:mm\:ss") | Out-File -FilePath $test_summary -Append - "Tests executed: $total_tests" | Out-File -FilePath $test_summary -Append - "Tests passed: $total_tests_passed" | Out-File -FilePath $test_summary -Append - "Tests failed: $total_tests_failed" | Out-File -FilePath $test_summary -Append - "Failed tests: $failed_testcases" | Out-File -FilePath $test_summary -Append - "Skipped known failed tests: $known_failed_testcases_skipped" | Out-File -FilePath $test_summary -Append - - Write-Output "Artifacts are saved to $ArtifactsPath" - - #output the summary - Write-Output "=================================" - cat $test_summary - Write-Output "=================================" -} -finally -{ - # remove temp test directory - if (!$SkipCleanup) - { - # remove temp test folder - &$ShellPath -c "rm -rf $BashTestsPath/temp_test" - - if(!$SkipInstallSSHD) { - # Uninstall the sshd, ssh-agent service - & "$PSScriptRoot\uninstall-sshd.ps1" - } - - # Remove the test environment variable - Remove-Item ENV:\SSH_TEST_ENVIRONMENT - - # Revert to user configured default shell. - if($user_default_shell) { - Set-ItemProperty -Path $registryPath -Name $dfltShell -Value $user_default_shell -Force - $out=(Get-ItemProperty -Path $registryPath -Name $dfltShell -ErrorAction SilentlyContinue) - if($out.$dfltShell -eq $user_default_shell) { - Write-Output "Reverted user configured default shell to $user_default_shell" - } else { - Write-Output "Failed to set HKLM:\Software\OpenSSH\DefaultShell to $user_default_shell" - } - } else { - Remove-ItemProperty -Path $registryPath -Name $dfltShell -ErrorAction SilentlyContinue - } - } - - Push-location $user_pwd -} diff --git a/contrib/win32/openssh/bash_tests_iterator.ps1 b/contrib/win32/openssh/bash_tests_iterator.ps1 new file mode 100644 index 000000000..eb9685b5f --- /dev/null +++ b/contrib/win32/openssh/bash_tests_iterator.ps1 @@ -0,0 +1,286 @@ +param ( + # Path to openssh binaries + [Parameter(Mandatory=$true)] [string] $OpenSSHBinPath, + # Path of regress folder which has all the bash testcases. + [Parameter(Mandatory=$true)] [string] $BashTestsPath, + # Path to CYGWIN / WSL. + [Parameter(Mandatory=$true)] [string] $ShellPath, + # Individual bash test file (Ex - connect.sh, scp.sh) + [Parameter(Mandatory=$false)] [string[]] $TestFilePath, + [Parameter(Mandatory=$false)] [string] $ArtifactsDirectoryPath=".", + [switch] $SkipCleanup, + [switch] $SkipInstallSSHD +) + +$ErrorActionPreference = 'Continue' + +# Resolve the relative paths +$OpenSSHBinPath = Resolve-Path $OpenSSHBinPath -ErrorAction Stop | select -ExpandProperty Path +$BashTestsPath = Resolve-Path $BashTestsPath -ErrorAction Stop | select -ExpandProperty Path +$ShellPath = Resolve-Path $ShellPath -ErrorAction Stop | select -ExpandProperty Path +$ArtifactsDirectoryPath = Resolve-Path $ArtifactsDirectoryPath -ErrorAction Stop | select -ExpandProperty Path +if ($TestFilePath) { + $TestFilePath = Resolve-Path $TestFilePath -ErrorAction Stop | select -ExpandProperty Path + # convert to bash format + $TestFilePath = $TestFilePath -replace "\\","/" +} + +# Make sure config.h exists. It is used in some bashstests (Ex - sftp-glob.sh, cfgparse.sh) +# first check in $BashTestsPath folder. If not then it's parent folder. If not then in the $OpenSSHBinPath +if(Test-Path "$BashTestsPath\config.h" -PathType Leaf) { + $configPath = "$BashTestsPath\config.h" +} elseif(Test-Path "$BashTestsPath\..\config.h" -PathType Leaf) { + $configPath = Resolve-Path "$BashTestsPath\..\config.h" -ErrorAction Stop | select -ExpandProperty Path +} elseif(Test-Path "$OpenSSHBinPath\config.h" -PathType Leaf) { + $configPath = "$OpenSSHBinPath\config.h" +} else { + Write-Error "Couldn't find config.h" + exit +} + +$user_pwd = pwd | select -ExpandProperty Path + +# If we are using a SKU with desired OpenSSH binaries then we can skip these steps. +if(!$SkipInstallSSHD) { + # Make sure install-sshd.ps1 exists. + # This is required only for ssh-agent related bash tests. + if(!(Test-Path "$PSScriptRoot\install-sshd.ps1" -PathType Leaf)) { + Write-Error "$PSScriptRoot\install-sshd.ps1 doesn't exists" + exit + } + + # Make sure uninstall-sshd.ps1 exists. + if(!(Test-Path "$PSScriptRoot\uninstall-sshd.ps1" -PathType Leaf)) { + Write-Error "$PSScriptRoot\uninstall-sshd.ps1 doesn't exists" + exit + } + + #copy to binary folder and execute install-sshd.ps1 + Copy-Item $PSScriptRoot\install-sshd.ps1 -Force $OpenSSHBinPath + Copy-Item $PSScriptRoot\uninstall-sshd.ps1 -Force $OpenSSHBinPath + + # We need ssh-agent to be installed as service to run some bash tests. + & "$OpenSSHBinPath\install-sshd.ps1" +} + +try +{ + # set the default shell + $registryPath = "HKLM:\Software\OpenSSH" + $dfltShell = "DefaultShell" + # Fetch the user configured default shell. + $out = (Get-ItemProperty -Path $registryPath -Name $dfltShell -ErrorAction SilentlyContinue) + if ($out) { + $user_default_shell = $out.$dfltShell + Write-Output "User configured default shell: $user_default_shell" + } + + if ($user_default_shell -ne $ShellPath) + { + if (!(Test-Path $registryPath)) { + # start and stop the sshd so that "HKLM:\Software\OpenSSH" registry path is created. + Start-Service sshd -ErrorAction Stop + Stop-Service sshd -ErrorAction SilentlyContinue + } + + Set-ItemProperty -Path $registryPath -Name $dfltShell -Value $ShellPath -Force + $out = (Get-ItemProperty -Path $registryPath -Name $dfltShell -ErrorAction SilentlyContinue) + if ($out.$dfltShell -ne $ShellPath) { + Write-Output "Failed to set HKLM:\Software\OpenSSH\DefaultShell to $ShellPath" + exit + } + + Write-Output "Successfully set the default shell (HKLM:\Software\OpenSSH\DefaultShell) to $ShellPath" + } + + # Prepend shell path to PATH. This is required to make the shell commands (like sleep, cat, etc) work properly. + $env:TEST_SHELL_PATH = $ShellPath -replace "\\","/" + $TEST_SHELL_DIR = split-path $ShellPath + if(!$env:path.StartsWith($TEST_SHELL_DIR, "CurrentCultureIgnoreCase")) + { + $env:path = $TEST_SHELL_DIR + ";" + $env:path + } + + $BashTestsPath = $BashTestsPath -replace "\\","/" + Push-location $BashTestsPath + + # BUILDDIR: config.h location. + # BUILDDIR is used by bashstests (Ex - sftp-glob.sh, cfgparse.sh) + $BUILDDIR = Resolve-Path(split-path $configpath) | select -ExpandProperty Path + $tmp = &$ShellPath -c pwd + if ($tmp.StartsWith("/cygdrive/")) { + $shell_drv_fmt = "/cygdrive/" # cygwin + $BUILDDIR = &$ShellPath -c "cygpath -u '$BUILDDIR'" + $OpenSSHBinPath_shell_fmt=&$ShellPath -c "cygpath -u '$OpenSSHBinPath'" + $BashTestsPath = &$ShellPath -c "cygpath -u '$BashTestsPath'" + } elseif ($tmp.StartsWith("/mnt/")) { + $shell_drv_fmt = "/mnt/" # WSL bash + $BUILDDIR = &$ShellPath -c "wslpath -u '$BUILDDIR'" + $OpenSSHBinPath_shell_fmt=&$ShellPath -c "wslpath -u '$OpenSSHBinPath'" + $BashTestsPath = &$ShellPath -c "wslpath -u '$BashTestsPath'" + } + + #set the environment variables. + $env:ShellPath = $ShellPath + $env:SSH_TEST_ENVIRONMENT = 1 + $env:TEST_SSH_TRACE = "yes" + $env:TEST_SHELL = "/bin/sh" + $env:TEST_SSH_PORT = 22 + $env:TEST_SSH_SSH = $OpenSSHBinPath_shell_fmt+"/ssh.exe" + $env:TEST_SSH_SSHD = $OpenSSHBinPath_shell_fmt+"/sshd.exe" + $env:TEST_SSH_SSHAGENT = $OpenSSHBinPath_shell_fmt+"/ssh-agent.exe" + $env:TEST_SSH_SSHADD = $OpenSSHBinPath_shell_fmt+"/ssh-add.exe" + $env:TEST_SSH_SSHKEYGEN = $OpenSSHBinPath_shell_fmt+"/ssh-keygen.exe" + $env:TEST_SSH_SSHKEYSCAN = $OpenSSHBinPath_shell_fmt+"/ssh-keyscan.exe" + $env:TEST_SSH_SFTP = $OpenSSHBinPath_shell_fmt+"/sftp.exe" + $env:TEST_SSH_SFTPSERVER = $OpenSSHBinPath_shell_fmt+"/sftp-server.exe" + $env:TEST_SSH_SCP = $OpenSSHBinPath_shell_fmt+"/scp.exe" + $env:BUILDDIR = $BUILDDIR + $env:TEST_WINDOWS_SSH = 1 + $user = &"$env:windir\system32\whoami.exe" + if($user.Contains($env:COMPUTERNAME.ToLower())) { + # for local accounts, skip COMPUTERNAME + $user = Split-Path $user -leaf + $env:TEST_SSH_USER = $user + } else { + # for domain user convert "domain\user" to "domain/user". + $user = $user -replace "\\","/" + $env:TEST_SSH_USER = $user + $env:TEST_SSH_USER_DOMAIN = Split-Path $user + } + + Write-Output "USER: $env:TEST_SSH_USER" + Write-Output "DOMAIN: $env:TEST_SSH_USER_DOMAIN" + Write-Output "OpenSSHBinPath: $OpenSSHBinPath_shell_fmt" + Write-Output "BUILDDIR: $BUILDDIR" + Write-Output "BashTestsPath: $BashTestsPath" + + # remove, create the temp test directory + $temp_test_path = "temp_test" + $null = Remove-Item -Recurse -Force $temp_test_path -ErrorAction SilentlyContinue + $null = New-Item -ItemType directory -Path $temp_test_path -Force -ErrorAction Stop + + # remove the summary, output files. + $bash_test_summary = "$ArtifactsDirectoryPath\bash_tests_summary.txt" + $bash_test_log_file = "$ArtifactsDirectoryPath\bash_tests_output.log" + $null = Remove-Item -Force $bash_test_summary -ErrorAction SilentlyContinue + $null = Remove-Item -Force $bash_test_log_file -ErrorAction SilentlyContinue + [int]$total_tests = 0 + [int]$total_tests_passed = 0 + [int]$total_tests_failed = 0 + [string]$failed_testcases = [string]::Empty + + # These are the known failed testcases. + $known_failed_testcases = @("agent.sh", "key-options.sh", "forward-control.sh", "integrity.sh", "krl.sh", "authinfo.sh") + $known_failed_testcases_skipped = @() + + $start_time = (Get-Date) + + if($TestFilePath) { + # User can specify individual test file path. + $all_tests = $TestFilePath + } else { + # picking the gawk.exe from bash folder. + # TODO - check if gawk.exe is present in WSL. + $all_tests = gawk.exe 'sub(/.*LTESTS=/,""""){f=1} f{print $1; if (!/\\\\/) exit}' Makefile + } + + Write-Output "" + + foreach ($test_file in $all_tests) { + if ($TestFilePath) { + $TEST = $test_file + } else { + if (!$test_file.Contains(".sh")) { + $TEST = $BashTestsPath + "/" + $test_file + ".sh" + } else { + $TEST = $BashTestsPath + "/" + $test_file + } + } + + $test_file_name = [System.IO.Path]::GetFileName($TEST) + if ($known_failed_testcases.Contains($test_file_name)) + { + Write-Output "Skip the known failed test:$test_file_name [$($all_tests.IndexOf($test_file) + 1) of $($all_tests.count)]" + $known_failed_testcases_skipped += "$test_file_name" + } + else + { + $msg = "Run $test_file_name [$($all_tests.IndexOf($test_file) + 1) of $($all_tests.count)] " + [System.DateTime]::Now + Write-Output $msg | Tee-Object -FilePath $bash_test_log_file -Append -ErrorAction Stop + + &$env:ShellPath -c "/usr/bin/sh $BashTestsPath/test-exec.sh $BashTestsPath/$temp_test_path $TEST 2>&1" | Out-File -FilePath $bash_test_log_file -Append -ErrorAction Stop + if ($?) + { + $msg = "$test_file_name PASSED " + [System.DateTime]::Now + Write-Output $msg | Tee-Object -FilePath $bash_test_log_file -Append -ErrorAction Stop + $total_tests_passed++ + } + else + { + $msg = "$test_file_name FAILED " + [System.DateTime]::Now + Write-Output $msg | Tee-Object -FilePath $bash_test_log_file -Append -ErrorAction Stop + $total_tests_failed++ + $failed_testcases = $failed_testcases + "$test_file_name " + } + } + + $total_tests++ + } + + $end_time = (Get-Date) + + # Create artifacts + $Global:bash_tests_summary = [ordered]@{ + "StartTime" = $start_time.ToString(); + "EndTime" = $end_time.ToString(); + "TotalExecutionTime" = (NEW-TIMESPAN -Start $start_time -End $end_time).ToString("hh\:mm\:ss"); + "TotalBashTests" = $total_tests; + "TotalBashTestsPassed" = $total_tests_passed; + "TotalBashTestsFailed" = $total_tests_failed; + "TotalBashTestsSkipped" = $known_failed_testcases_skipped.Count; + "FailedBashTests" = $failed_testcases; + "SkippedBashTests" = $known_failed_testcases_skipped -join ', '; + "BashTestSummaryFile" = $bash_test_summary + "BashTestLogFile" = $bash_test_log_file + } + + $Global:bash_tests_summary | ConvertTo-Json | Out-File -FilePath $bash_test_summary + + #output the summary + Write-Output "`n============================================" + Get-Content -Raw $bash_test_summary + Write-Output "============================================`n" +} +finally +{ + # remove temp test directory + if (!$SkipCleanup) + { + # remove temp test folder + &$ShellPath -c "rm -rf $BashTestsPath/temp_test" + + if(!$SkipInstallSSHD) { + # Uninstall the sshd, ssh-agent service + & "$PSScriptRoot\uninstall-sshd.ps1" + } + + # Remove the test environment variable + Remove-Item ENV:\SSH_TEST_ENVIRONMENT + + # Revert to user configured default shell. + if($user_default_shell) { + Set-ItemProperty -Path $registryPath -Name $dfltShell -Value $user_default_shell -Force + $out=(Get-ItemProperty -Path $registryPath -Name $dfltShell -ErrorAction SilentlyContinue) + if($out.$dfltShell -eq $user_default_shell) { + Write-Output "Reverted user configured default shell to $user_default_shell" + } else { + Write-Output "Failed to set HKLM:\Software\OpenSSH\DefaultShell to $user_default_shell" + } + } else { + Remove-ItemProperty -Path $registryPath -Name $dfltShell -ErrorAction SilentlyContinue + } + } + + Push-location $user_pwd +} diff --git a/contrib/win32/win32compat/misc.c b/contrib/win32/win32compat/misc.c index 552c19f09..4f900232e 100644 --- a/contrib/win32/win32compat/misc.c +++ b/contrib/win32/win32compat/misc.c @@ -1709,166 +1709,187 @@ build_exec_command(const char * command) return cmd_sp; } -/* - * cmd is internally decoarated with a set of '"' - * to account for any spaces within the commandline - * the double quotes and backslash is escaped if needed - * this decoration is done only when additional arguments are passed in argv -*/ -char * -build_commandline_string(const char* cmd, char *const argv[], BOOLEAN prepend_module_path) -{ - char *cmdline, *t, *tmp = NULL, *path = NULL, *ret = NULL; - char * const *t1; - DWORD cmdline_len = 0, path_len = 0; - int add_module_path = 0; +/* +* cmd is internally decoarated with a set of '"' +* to account for any spaces within the commandline +* the double quotes and backslash is escaped if needed +* this decoration is done only when additional arguments are passed in argv +*/ +char * +build_commandline_string(const char* cmd, char *const argv[], BOOLEAN prepend_module_path) +{ + char *cmdline, *t, *tmp = NULL, *path = NULL, *ret = NULL; + char * const *t1; + DWORD cmdline_len = 0, path_len = 0; + int add_module_path = 0; + + if (!cmd) { + error("%s invalid argument cmd:%s", __func__, cmd); + return NULL; + } + + if (!(path = _strdup(cmd))) { + error("failed to duplicate %s", cmd); + return NULL; + } + + path_len = (DWORD)strlen(path); + + if (is_bash_test_env()) { + memset(path, 0, path_len + 1); + bash_to_win_path(cmd, path, path_len + 1); + path_len = (DWORD)strlen(path); + } + + if (!is_absolute_path(path) && prepend_module_path) + add_module_path = 1; + + /* compute total cmdline len*/ + if (add_module_path) + cmdline_len += (DWORD)strlen(__progdir) + 1 + (DWORD)strlen(path) + 1 + 2; + else + cmdline_len += (DWORD)strlen(path) + 1 + 2; + + if (argv) { + t1 = argv; + while (*t1) { + char *p = *t1++; + for (int i = 0; i < (int)strlen(p); i++) { + if (p[i] == '\\') { + char * b = p + i; + int additional_backslash = 0; + int backslash_count = 0; + /* + Backslashes are interpreted literally, unless they immediately + precede a double quotation mark. + */ + while (b != NULL && *b == '\\') { + backslash_count++; + b++; + if (b != NULL && *b == '\"') { + additional_backslash = 1; + break; + } + } + cmdline_len += backslash_count * (additional_backslash + 1); + i += backslash_count - 1; + } + else if (p[i] == '\"') + /* backslash will be added for every double quote.*/ + cmdline_len += 2; + else + cmdline_len++; + } + cmdline_len += 1 + 2; /*for "around cmd arg and traling space*/ + } + } + + if ((cmdline = malloc(cmdline_len)) == NULL) { + errno = ENOMEM; + goto cleanup; + } + + t = cmdline; + + *t++ = '\"'; + if (add_module_path) { + /* add current module path to start if needed */ + memcpy(t, __progdir, strlen(__progdir)); + t += strlen(__progdir); + *t++ = '\\'; + } + + if (path[0] != '\"') { + /* If path is then we should add double quotes after i.e., "" should be passed to CreateProcess(). + * Example - If path is C:\cygwin64\bin\bash.exe /cygdrive/e/openssh-portable-latestw_all/openssh-portable/regress/scp-ssh-wrapper.sh then + * we should pass "C:\cygwin64\bin\bash.exe" /cygdrive/e/openssh-portable-latestw_all/openssh-portable/regress/scp-ssh-wrapper.sh + * to the CreateProcess() otherwise CreateProcess() will fail with error code 2. + */ + if (strstr(path, ".exe") && (tmp = strstr(strstr(path, ".exe"), " "))) + { + size_t tmp_pos = tmp - path; + memcpy(t, path, tmp_pos); + t += tmp_pos; + *t++ = '\"'; + memcpy(t, tmp, strlen(path) - tmp_pos); + t += (strlen(path) - tmp_pos); + } + else { + memcpy(t, path, path_len); + t += path_len; + *t++ = '\"'; + } + } + else { + /*path already contains "*/ + memcpy(t, path + 1, path_len - 1); + t += path_len - 1; + } + + *t = '\0'; + t = cmdline + strlen(cmdline); + + if (argv) { + t1 = argv; + while (*t1) { + *t++ = ' '; + char * p1 = *t1++; + BOOL add_quotes = FALSE; + /* leave as is if the command is surrounded by single quotes*/ + if (p1[0] != '\'') + for (int i = 0; i < (int)strlen(p1); i++) { + if (p1[i] == ' ') { + add_quotes = TRUE; + break; + } + } + if (add_quotes) + *t++ = '\"'; + for (int i = 0; i < (int)strlen(p1); i++) { + if (p1[i] == '\\') { + char * b = p1 + i; + int additional_backslash = 0; + int backslash_count = 0; + /* + * Backslashes are interpreted literally, unless they immediately + * precede a double quotation mark. + */ + while (b != NULL && *b == '\\') { + backslash_count++; + b++; + if (b != NULL && *b == '\"') { + additional_backslash = 1; + break; + } + } + i += backslash_count - 1; + int escaped_backslash_count = backslash_count * (additional_backslash + 1); + while (escaped_backslash_count--) + *t++ = '\\'; + } + else if (p1[i] == '\"') { + /* Add backslash for every double quote.*/ + *t++ = '\\'; + *t++ = '\"'; + } + else + *t++ = p1[i]; + } + if (add_quotes) + *t++ = '\"'; + } + } + *t = '\0'; + ret = cmdline; + cmdline = NULL; +cleanup: + if (path) + free(path); + if (cmdline) + free(cmdline); + return ret; +} - if (!cmd) { - error("%s invalid argument cmd:%s", __func__, cmd); - return NULL; - } - - if (!(path = _strdup(cmd))) { - error("failed to duplicate %s", cmd); - return NULL; - } - - path_len = (DWORD)strlen(path); - - if (is_bash_test_env()) { - memset(path, 0, path_len + 1); - bash_to_win_path(cmd, path, path_len + 1); - } - - if (!is_absolute_path(path) && prepend_module_path) - add_module_path = 1; - - /* compute total cmdline len*/ - if (add_module_path) - cmdline_len += (DWORD)strlen(__progdir) + 1 + (DWORD)strlen(path) + 1 + 2; - else - cmdline_len += (DWORD)strlen(path) + 1 + 2; - - if (argv) { - t1 = argv; - while (*t1) { - char *p = *t1++; - for (int i = 0; i < (int)strlen(p); i++) { - if (p[i] == '\\') { - char * b = p + i; - int additional_backslash = 0; - int backslash_count = 0; - /* - Backslashes are interpreted literally, unless they immediately - precede a double quotation mark. - */ - while (b != NULL && *b == '\\') { - backslash_count++; - b++; - if (b != NULL && *b == '\"') { - additional_backslash = 1; - break; - } - } - cmdline_len += backslash_count * (additional_backslash + 1); - i += backslash_count - 1; - } - else if (p[i] == '\"') - /* backslash will be added for every double quote.*/ - cmdline_len += 2; - else - cmdline_len++; - } - cmdline_len += 1 + 2; /*for "around cmd arg and traling space*/ - } - } - - if ((cmdline = malloc(cmdline_len)) == NULL) { - errno = ENOMEM; - goto cleanup; - } - t = cmdline; - *t++ = '\"'; - if (add_module_path) { - /* add current module path to start if needed */ - memcpy(t, __progdir, strlen(__progdir)); - t += strlen(__progdir); - *t++ = '\\'; - } - if (path[0] != '\"') { - memcpy(t, path, path_len); - t += path_len; - *t++ = '\"'; - } - else { - /*path already contains "*/ - memcpy(t, path + 1, path_len - 1); - t += path_len - 1; - } - - *t = '\0'; - t = cmdline + strlen(cmdline); - - if (argv) { - t1 = argv; - while (*t1) { - *t++ = ' '; - char * p1 = *t1++; - BOOL add_quotes = FALSE; - /* leave as is if the command is surrounded by single quotes*/ - if (p1[0] != '\'') - for (int i = 0; i < (int)strlen(p1); i++) { - if (p1[i] == ' ') { - add_quotes = TRUE; - break; - } - } - if (add_quotes) - *t++ = '\"'; - for (int i = 0; i < (int)strlen(p1); i++) { - if (p1[i] == '\\') { - char * b = p1 + i; - int additional_backslash = 0; - int backslash_count = 0; - /* - * Backslashes are interpreted literally, unless they immediately - * precede a double quotation mark. - */ - while (b != NULL && *b == '\\') { - backslash_count++; - b++; - if (b != NULL && *b == '\"') { - additional_backslash = 1; - break; - } - } - i += backslash_count - 1; - int escaped_backslash_count = backslash_count * (additional_backslash + 1); - while (escaped_backslash_count--) - *t++ = '\\'; - } - else if (p1[i] == '\"') { - /* Add backslash for every double quote.*/ - *t++ = '\\'; - *t++ = '\"'; - } - else - *t++ = p1[i]; - } - if (add_quotes) - *t++ = '\"'; - } - } - *t = '\0'; - ret = cmdline; - cmdline = NULL; -cleanup: - if (path) - free(path); - if (cmdline) - free(cmdline); - return ret; -} BOOL is_bash_test_env() { diff --git a/contrib/win32/win32compat/termio.c b/contrib/win32/win32compat/termio.c index 1288a868b..9ecd57679 100644 --- a/contrib/win32/win32compat/termio.c +++ b/contrib/win32/win32compat/termio.c @@ -127,6 +127,11 @@ ReadThread(_In_ LPVOID lpParameter) pio->sync_read_status.error = GetLastError(); goto done; } + + /* If there is no data to be read then set the error to ERROR_HANDLE_EOF */ + if (!pio->sync_read_status.transferred) { + pio->sync_read_status.error = ERROR_HANDLE_EOF; + } } done: diff --git a/contrib/win32/win32compat/w32fd.c b/contrib/win32/win32compat/w32fd.c index 4e1770ed3..d08ca6599 100644 --- a/contrib/win32/win32compat/w32fd.c +++ b/contrib/win32/win32compat/w32fd.c @@ -1044,7 +1044,7 @@ char * build_commandline_string(const char* cmd, char *const argv[], BOOLEAN pre * spawned child will run as as_user if its not NULL */ static int -spawn_child_internal(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) { PROCESS_INFORMATION pi; STARTUPINFOW si; @@ -1262,7 +1262,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(argv[0], 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); if (i == -1) goto cleanup; if (pidp) diff --git a/contrib/win32/win32compat/win32_groupaccess.c b/contrib/win32/win32compat/win32_groupaccess.c index f18377ebd..be051b30e 100644 --- a/contrib/win32/win32compat/win32_groupaccess.c +++ b/contrib/win32/win32compat/win32_groupaccess.c @@ -115,7 +115,6 @@ get_user_groups() } for (DWORD i = 0; i < group_buf->GroupCount; i++) { - /* only bother with group thats are 'enabled' from a security perspective */ if ((group_buf->Groups[i].Attributes & SE_GROUP_ENABLED) == 0 || !IsValidSid(group_buf->Groups[i].Sid)) @@ -278,6 +277,7 @@ int ga_match_pattern_list(const char *group_pattern) { int i, found = 0; + char *tmp = NULL; /* group retrieval is expensive, optmizing the common case scenario - only one group with no wild cards and no negation */ if (!strchr(group_pattern, ',') && !strchr(group_pattern, '?') && @@ -287,8 +287,15 @@ ga_match_pattern_list(const char *group_pattern) if (get_user_groups() == -1) fatal("unable to retrieve group info for user %s", user_name); + /* For domain groups we need special handling. + * We support both "domain\group_name" and "domain/group_name" formats. + */ + if (tmp = strstr(group_pattern, "/")) + *tmp = '\\'; + for (i = 0; i < ngroups; i++) { - switch (match_pattern_list(groups_byname[i], group_pattern, 0)) { + /* Group names are case insensitive */ + switch (match_pattern_list(groups_byname[i], group_pattern, 1)) { case -1: return 0; /* Negated match wins */ case 0: diff --git a/match.c b/match.c index 88ec42c0b..0953af09d 100644 --- a/match.c +++ b/match.c @@ -177,8 +177,14 @@ match_usergroup_pattern_list(const char *string, const char *pattern) #ifdef HAVE_CYGWIN /* Windows usernames may be Unicode and are not case sensitive */ return cygwin_ug_match_pattern_list(string, pattern); -#elseif WINDOWS - if (match_pattern_list(ci->user, arg, 1) != 1) +#elif WINDOWS + /* We support both domain/username and domain\\username format */ + char *tmp = NULL; + if (tmp = strstr(pattern, "/")) + *tmp = '\\'; + + /* Windows usernames are case insensitive */ + return match_pattern_list(string, pattern, 1); #else /* Case insensitive match */ return match_pattern_list(string, pattern, 0); diff --git a/openbsd-compat/timingsafe_bcmp.c b/openbsd-compat/timingsafe_bcmp.c index 7e28c0e2a..7c6a949d3 100644 --- a/openbsd-compat/timingsafe_bcmp.c +++ b/openbsd-compat/timingsafe_bcmp.c @@ -26,8 +26,14 @@ timingsafe_bcmp(const void *b1, const void *b2, size_t n) const unsigned char *p1 = b1, *p2 = b2; int ret = 0; - for (; n > 0; n--) + for (; n > 0; n--) { +#ifdef WINDOWS + if (*p1 == '\r' && *(p1 + 1) == '\n' && *p2 == '\n') + p1++; +#endif // WINDOWS ret |= *p1++ ^ *p2++; + } + return (ret != 0); } diff --git a/regress/cfgmatch.sh b/regress/cfgmatch.sh index 8921efc11..660a632cd 100644 --- a/regress/cfgmatch.sh +++ b/regress/cfgmatch.sh @@ -25,7 +25,7 @@ start_client() if test $n -gt 60; then if [ "$os" == "windows" ]; then # We can't kill windows process from cygwin / wsl so use "stop-process" - powershell.exe /c "stop-process -id $client_pid" + powershell.exe /c "stop-process -id $client_pid" >/dev/null 2>&1 else kill $client_pid fi @@ -39,8 +39,8 @@ stop_client() pid=`cat $pidfile` if [ "$os" == "windows" ]; then # We can't kill windows process from cygwin / wsl so use "stop-process" - powershell.exe /c "stop-process -id $pid" - powershell.exe /c "stop-process -name sleep" + powershell.exe /c "stop-process -id $pid" >/dev/null 2>&1 + powershell.exe /c "stop-process -name sleep" >/dev/null 2>&1 else if [ ! -z "$pid" ]; then kill $pid @@ -174,6 +174,9 @@ for i in $params; do trace "test spec $spec" result=`${SUDO} ${SSHD} -f $OBJ/sshd_config -T -C "$spec" | \ awk '$1=="banner"{print $2}'` + if [ "$os" == "windows" ]; then + result=${result/$'\r'/} # remove CR (carriage return) + fi if [ "$result" != "$expected" ]; then fail "match $config expected $expected got $result" fi diff --git a/regress/cfgmatchlisten.sh b/regress/cfgmatchlisten.sh index a4fd66b32..aef97b710 100644 --- a/regress/cfgmatchlisten.sh +++ b/regress/cfgmatchlisten.sh @@ -29,8 +29,12 @@ start_client() sleep 1 n=`expr $n + 1` if test $n -gt 60; then - kill $client_pid - fatal "timeout waiting for background ssh" + if [ "$os" == "windows" ]; then + powershell.exe /c "stop-process -Id $client_pid -Force" >/dev/null 2>&1 + else + kill $client_pid + fatal "timeout waiting for background ssh" + fi fi done return $r @@ -53,10 +57,14 @@ expect_client_fail() stop_client() { pid=`cat $pidfile` - if [ ! -z "$pid" ]; then - kill $pid + if [ "$os" == "windows" ]; then + powershell.exe /c "stop-process -Id $pid -Force" >/dev/null 2>&1 + else + if [ ! -z "$pid" ]; then + kill $pid + fi + wait fi - wait } cp $OBJ/sshd_proxy $OBJ/sshd_proxy_bak diff --git a/regress/hostkey-rotate.sh b/regress/hostkey-rotate.sh index 4e97938e9..471d97a27 100644 --- a/regress/hostkey-rotate.sh +++ b/regress/hostkey-rotate.sh @@ -4,7 +4,11 @@ tid="hostkey rotate" # Need full names here since they are used in HostKeyAlgorithms -HOSTKEY_TYPES="`${SSH} -Q key-plain`" +if [ "$os" == "windows" ]; then + HOSTKEY_TYPES=`${SSH} -Q key-plain | sed 's/\r$//'` # remove CR (carriage return) +else + HOSTKEY_TYPES=`${SSH} -Q key-plain` +fi rm -f $OBJ/hkr.* $OBJ/ssh_proxy.orig @@ -21,9 +25,6 @@ trace "prepare hostkeys" nkeys=0 all_algs="" for k in $HOSTKEY_TYPES; do - if [ "$os" == "windows" ]; then - k=`echo $k | sed 's/\r$//'` # remove CR (carriage return) - fi ${SSHKEYGEN} -qt $k -f $OBJ/hkr.$k -N '' || fatal "ssh-keygen $k" echo "Hostkey $OBJ/hkr.${k}" >> $OBJ/sshd_proxy.orig nkeys=`expr $nkeys + 1` diff --git a/regress/sftp-perm.sh b/regress/sftp-perm.sh index 34d68fc5c..458d7ea3a 100644 --- a/regress/sftp-perm.sh +++ b/regress/sftp-perm.sh @@ -105,11 +105,11 @@ if [ "$os" == "windows" ]; then "powershell.exe /c \"!(Get-ChildItem $`windows_path $OBJ`/copy).IsReadOnly\" 1>/dev/null" else ro_test \ - "setstat" \ - "chmod 0700 $COPY" \ - "touch $COPY; chmod 0400 $COPY" \ - "test -x $COPY" \ - "test ! -x $COPY" + "setstat" \ + "chmod 0700 $COPY" \ + "touch $COPY; chmod 0400 $COPY" \ + "test -x $COPY" \ + "test ! -x $COPY" fi ro_test \ diff --git a/regress/sshcfgparse.sh b/regress/sshcfgparse.sh index fd85d8805..566d5b701 100644 --- a/regress/sshcfgparse.sh +++ b/regress/sshcfgparse.sh @@ -72,31 +72,55 @@ test "$f" = "no" || fail "clearallforwardings override" verbose "user first match" user=`awk '$1=="User" {print $2}' $OBJ/ssh_config` f=`${SSH} -GF $OBJ/ssh_config host | awk '/^user /{print $2}'` +if [ "$os" == "windows" ]; then + f=${f/$'\r'/} # remove CR (carriage return) +fi test "$f" = "$user" || fail "user from config, expected '$user' got '$f'" f=`${SSH} -GF $OBJ/ssh_config -o user=foo -l bar baz@host | awk '/^user /{print $2}'` +if [ "$os" == "windows" ]; then + f=${f/$'\r'/} # remove CR (carriage return) +fi test "$f" = "foo" || fail "user first match -oUser, expected 'foo' got '$f' " f=`${SSH} -GF $OBJ/ssh_config -lbar baz@host user=foo baz@host | awk '/^user /{print $2}'` +if [ "$os" == "windows" ]; then + f=${f/$'\r'/} # remove CR (carriage return) +fi test "$f" = "bar" || fail "user first match -l, expected 'bar' got '$f'" f=`${SSH} -GF $OBJ/ssh_config baz@host -o user=foo -l bar baz@host | awk '/^user /{print $2}'` +if [ "$os" == "windows" ]; then + f=${f/$'\r'/} # remove CR (carriage return) +fi test "$f" = "baz" || fail "user first match user@host, expected 'baz' got '$f'" verbose "pubkeyacceptedkeytypes" # Default set f=`${SSH} -GF none host | awk '/^pubkeyacceptedkeytypes /{print $2}'` +if [ "$os" == "windows" ]; then + f=${f/$'\r'/} # remove CR (carriage return) +fi expect_result_present "$f" "ssh-ed25519" "ssh-ed25519-cert-v01.*" expect_result_absent "$f" "ssh-dss" # Explicit override f=`${SSH} -GF none -opubkeyacceptedkeytypes=ssh-ed25519 host | \ awk '/^pubkeyacceptedkeytypes /{print $2}'` +if [ "$os" == "windows" ]; then + f=${f/$'\r'/} # remove CR (carriage return) +fi expect_result_present "$f" "ssh-ed25519" expect_result_absent "$f" "ssh-ed25519-cert-v01.*" "ssh-dss" # Removal from default set f=`${SSH} -GF none -opubkeyacceptedkeytypes=-ssh-ed25519-cert* host | \ awk '/^pubkeyacceptedkeytypes /{print $2}'` +if [ "$os" == "windows" ]; then + f=${f/$'\r'/} # remove CR (carriage return) +fi expect_result_present "$f" "ssh-ed25519" expect_result_absent "$f" "ssh-ed25519-cert-v01.*" "ssh-dss" f=`${SSH} -GF none -opubkeyacceptedkeytypes=-ssh-ed25519 host | \ awk '/^pubkeyacceptedkeytypes /{print $2}'` +if [ "$os" == "windows" ]; then + f=${f/$'\r'/} # remove CR (carriage return) +fi expect_result_present "$f" "ssh-ed25519-cert-v01.*" expect_result_absent "$f" "ssh-ed25519" "ssh-dss" # Append to default set. @@ -104,10 +128,16 @@ expect_result_absent "$f" "ssh-ed25519" "ssh-dss" if [ "$dsa" = "1" ]; then f=`${SSH} -GF none -opubkeyacceptedkeytypes=+ssh-dss-cert* host | \ awk '/^pubkeyacceptedkeytypes /{print $2}'` + if [ "$os" == "windows" ]; then + f=${f/$'\r'/} # remove CR (carriage return) + fi expect_result_present "$f" "ssh-ed25519" "ssh-dss-cert-v01.*" expect_result_absent "$f" "ssh-dss" f=`${SSH} -GF none -opubkeyacceptedkeytypes=+ssh-dss host | \ awk '/^pubkeyacceptedkeytypes /{print $2}'` + if [ "$os" == "windows" ]; then + f=${f/$'\r'/} # remove CR (carriage return) + fi expect_result_present "$f" "ssh-ed25519" "ssh-ed25519-cert-v01.*" "ssh-dss" expect_result_absent "$f" "ssh-dss-cert-v01.*" fi diff --git a/regress/test-exec.sh b/regress/test-exec.sh index 09fa5834d..322f89a72 100644 --- a/regress/test-exec.sh +++ b/regress/test-exec.sh @@ -362,8 +362,15 @@ stop_sshd () make_tmpdir () { - SSH_REGRESS_TMP="$($OBJ/mkdtemp openssh-XXXXXXXX)" || \ - fatal "failed to create temporary directory" + if [ "$os" == "windows" ]; then + powershell.exe /c "New-Item -Path $OBJ\openssh-XXXXXXXX -ItemType Directory -Force" >/dev/null 2>&1 + if [ $? -ne 0 ]; then + fatal "failed to create temporary directory" + fi + else + SSH_REGRESS_TMP="$($OBJ/mkdtemp openssh-XXXXXXXX)" || \ + fatal "failed to create temporary directory" + fi } # helper @@ -514,6 +521,7 @@ rm -f $OBJ/known_hosts $OBJ/authorized_keys_$USER SSH_KEYTYPES=`$SSH -Q key-plain` if [ "$os" == "windows" ]; then + SSH_KEYTYPES=`echo $SSH_KEYTYPES | tr -d '\r','\n'` # remove \r\n first_key_type=${SSH_KEYTYPES%% *} if [ "x$USER_DOMAIN" != "x" ]; then # For domain user, create folders @@ -529,9 +537,14 @@ if [ "$os" == "windows" ]; then fi fi +if [ "$os" == "windows" ]; then + OBJ_WIN_FORMAT=`windows_path $OBJ` +fi + for t in ${SSH_KEYTYPES}; do # generate user key trace "generating key type $t" + if [ ! -f $OBJ/$t ] || [ ${SSHKEYGEN_BIN} -nt $OBJ/$t ]; then rm -f $OBJ/$t ${SSHKEYGEN} -q -N '' -t $t -f $OBJ/$t ||\ @@ -552,7 +565,7 @@ for t in ${SSH_KEYTYPES}; do $SUDO cp $OBJ/$t $OBJ/host.$t if [ "$os" == "windows" ]; then # set the file permissions (ACLs) properly - powershell.exe /c "get-acl `windows_path $OBJ`/$t | set-acl `windows_path $OBJ`/host.$t" + powershell.exe /c "get-acl $OBJ_WIN_FORMAT/$t | set-acl $OBJ_WIN_FORMAT/host.$t" fi echo HostKey $OBJ/host.$t >> $OBJ/sshd_config @@ -563,7 +576,7 @@ done if [ "$os" == "windows" ]; then # set the file permissions (ACLs) properly - powershell.exe /c "get-acl `windows_path $OBJ`/$first_key_type | set-acl `windows_path $OBJ`/authorized_keys_$USER" + powershell.exe /c "get-acl $OBJ_WIN_FORMAT/$first_key_type | set-acl $OBJ_WIN_FORMAT/authorized_keys_$USER" fi # Activate Twisted Conch tests if the binary is present @@ -626,7 +639,7 @@ fi ( cat $OBJ/ssh_config if [ "$os" == "windows" ]; then - echo proxycommand `windows_path ${SSHD}` -i -f `windows_path $OBJ`/sshd_proxy + echo proxycommand `windows_path ${SSHD}` -i -f $OBJ_WIN_FORMAT/sshd_proxy else echo proxycommand ${SUDO} sh ${SRC}/sshd-log-wrapper.sh ${TEST_SSHD_LOGFILE} ${SSHD} -i -f $OBJ/sshd_proxy fi diff --git a/regress/unittests/win32compat/miscellaneous_tests.c b/regress/unittests/win32compat/miscellaneous_tests.c index 00763b8d9..8ac60090f 100644 --- a/regress/unittests/win32compat/miscellaneous_tests.c +++ b/regress/unittests/win32compat/miscellaneous_tests.c @@ -364,7 +364,7 @@ test_build_commandline_string() ASSERT_STRING_EQ(out, "\"c:\\windows\\system32\\cmd.exe\" /c arg"); free(out); out = build_commandline_string("cmd.exe /c ping.exe", NULL, FALSE); - ASSERT_STRING_EQ(out, "\"cmd.exe /c ping.exe\""); + ASSERT_STRING_EQ(out, "\"cmd.exe\" /c ping.exe"); sprintf_s(in, PATH_MAX, "\"%s\\%s\"", __progdir, "ssh-shellhost.exe\" -c \"arg1 arg2\""); out = build_commandline_string(in, NULL, TRUE); ASSERT_STRING_EQ(out, in); diff --git a/sftp-server.c b/sftp-server.c index 8c4029efb..f17856089 100644 --- a/sftp-server.c +++ b/sftp-server.c @@ -1181,7 +1181,12 @@ process_realpath(u_int32_t id) } debug3("request %u: realpath", id); verbose("realpath \"%s\"", path); + +#ifdef WINDOWS + if (realpath(path, resolvedname) == NULL) { +#else if (sftp_realpath(path, resolvedname) == NULL) { +#endif // WINDOWS send_status(id, errno_to_portable(errno)); } else { Stat s;