diff --git a/.github/workflows/windows-check.yml b/.github/workflows/windows-check.yml index aed2420da..89ebe096d 100644 --- a/.github/workflows/windows-check.yml +++ b/.github/workflows/windows-check.yml @@ -88,6 +88,12 @@ jobs: timeout-minutes: 2 run: .\sshd_login_grace_test.ps1 -SshdExe "$env:SSHD_PATH" + - name: Test wolfsshd -D option parsing on Windows + working-directory: ${{ github.workspace }}\wolfssh\apps\wolfsshd\test + shell: pwsh + timeout-minutes: 2 + run: .\sshd_dash_d_test.ps1 -SshdExe "$env:SSHD_PATH" + # Build and run the self-contained unit tests with the MSVC AddressSanitizer. # This is the only job that executes wolfSSH tests under a sanitizer on # Windows, where the USE_WINDOWS_API console code (e.g. wolfSSH_DoOSC) is @@ -220,3 +226,83 @@ jobs: if ($LASTEXITCODE -ne 0) { throw "$t failed under ASAN (exit $LASTEXITCODE)" } } + # Build and run the autotools regression tests under MSYS2 MinGW64. MinGW + # defines _WIN32, so wolfssh/settings.h turns on USE_WINDOWS_API and the + # Windows-only coverage in tests/regress.c (TestSftpWindowsOpenFlagMatrix, + # which walks the RecvOpen CREAT/EXCL/TRUNC/APPEND matrix against the + # CreateFile() disposition table) actually compiles and runs. The MSVC + # solution has no regress project and regress.c does not build with cl (it + # uses arpa/inet.h and unistd.h), so without this job that matrix runs in no + # CI at all. wolfsshd is left out of the build: its autotools path is not + # MinGW-clean and the MSVC solution already covers it. + mingw-regress: + name: MSYS2 MinGW64 regression tests + runs-on: windows-latest + timeout-minutes: 40 + + defaults: + run: + shell: msys2 {0} + + steps: + - name: Set up MSYS2 MinGW64 + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: false + install: >- + base-devel + autotools + git + mingw-w64-x86_64-gcc + mingw-w64-x86_64-pkgconf + + - name: Checkout wolfssl + uses: actions/checkout@v4 + with: + repository: wolfssl/wolfssl + path: wolfssl + + - name: Build and install wolfssl + working-directory: wolfssl + run: | + ./autogen.sh + # --enable-all pulls in --enable-crl-monitor, which wolfSSL's configure + # rejects on MinGW (it is limited to linux, OS X, and freebsd). Turn it + # back off explicitly; wolfSSH does not use the CRL monitor. + ./configure --enable-all --disable-crl-monitor \ + --enable-static --disable-shared \ + --prefix="$HOME/wolfssl-install" + make -j$(nproc) + make install + + - name: Checkout wolfssh + uses: actions/checkout@v4 + with: + path: wolfssh + + - name: Build and run the regression tests + working-directory: wolfssh + run: | + # Run autoreconf directly instead of ./autogen.sh: for a git checkout + # autogen.sh exports WARNINGS="all,error", turning autotools warnings + # into errors that the MSYS2 automake can trip on. + autoreconf -ivf + # wolfssl is a static archive here, so its Windows socket (ws2_32) and + # certificate store (crypt32) references are only resolved when this + # configure's own AC_CHECK_LIB and later link steps pull them in too. + ./configure --enable-sftp \ + CPPFLAGS="-I$HOME/wolfssl-install/include" \ + LDFLAGS="-L$HOME/wolfssl-install/lib" \ + LIBS="-lws2_32 -lcrypt32" + # MinGW's EXEEXT is ".exe", so the check_PROGRAMS targets automake + # generates are tests/regress.test.exe and tests/unit.test.exe, not + # the extension-less names make would use on a POSIX host. + make -j$(nproc) tests/regress.test.exe tests/unit.test.exe + ./tests/regress.test.exe + ./tests/unit.test.exe + + - name: Show config.log on failure + if: failure() + working-directory: wolfssh + run: cat config.log diff --git a/apps/wolfsshd/test/sshd_dash_d_test.ps1 b/apps/wolfsshd/test/sshd_dash_d_test.ps1 new file mode 100644 index 000000000..9be75fe4c --- /dev/null +++ b/apps/wolfsshd/test/sshd_dash_d_test.ps1 @@ -0,0 +1,115 @@ +#!/usr/bin/env pwsh +# +# Windows regression test for wolfsshd -D (foreground) option parsing. +# +# On Windows, StartSSHD() rebuilds argv from GetCommandLineW(); a bug in that +# path left -D (foreground) mode parsing the raw wide command line, so -f and +# -p were silently ignored and the daemon fell back to its compiled-in +# defaults. This test starts wolfsshd with -D and a config file at a +# non-default path whose Port line differs from the -p value, then checks that +# the listener comes up on the -p port. That only happens if -D mode parsed +# both -f (to find the config) and -p (to override the config's Port). +# +# No Windows user account or authorized key is required: the check is that the +# daemon binds the requested port, not that a session authenticates. +# +# Usage: +# pwsh sshd_dash_d_test.ps1 -SshdExe [-Port N] [-ConfPort N] +# (SshdExe also accepts the SSHD_PATH environment variable.) + +param( + [string]$SshdExe = $env:SSHD_PATH, + [int]$Port = 22335, + [int]$ConfPort = 22336 +) + +$ErrorActionPreference = "Stop" +$exitCode = 1 + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$repoRoot = (Resolve-Path (Join-Path $scriptDir "..\..\..")).Path +$keyPath = (Resolve-Path (Join-Path $repoRoot "keys\server-key.pem")).Path +$confFile = Join-Path $scriptDir "sshd_config_test_dash_d" +$authFile = Join-Path $scriptDir "authorized_keys_test_dash_d" + +if (-not $SshdExe -or -not (Test-Path $SshdExe)) { + Write-Host "ERROR: wolfsshd.exe not found (pass -SshdExe or set SSHD_PATH)" + exit 1 +} + +if ($Port -eq $ConfPort) { + Write-Host "ERROR: -Port and -ConfPort must differ so the test can tell them apart" + exit 1 +} + +# The config's Port is deliberately not the port we probe. If -p is parsed it +# wins (wolfsshd only reads the config Port when none was given on the command +# line), so a listener on $Port proves the -p override took effect. +@" +Port $ConfPort +Protocol 2 +PermitRootLogin yes +PasswordAuthentication yes +UseDNS no +HostKey $keyPath +AuthorizedKeysFile $authFile +"@ | Out-File -FilePath $confFile -Encoding ASCII + +"" | Out-File -FilePath $authFile -Encoding ASCII + +# -D selects the non-service (foreground) path on Windows. +$sshd = Start-Process -FilePath $SshdExe ` + -ArgumentList "-D", "-f", "`"$confFile`"", "-p", "$Port" ` + -NoNewWindow -PassThru + +try { + $up = $false + for ($i = 0; $i -lt 20; $i++) { + if ($sshd.HasExited) { + throw "wolfsshd exited early (code $($sshd.ExitCode)); -D option parsing likely failed" + } + try { + $probe = New-Object System.Net.Sockets.TcpClient + $probe.Connect("127.0.0.1", $Port) + $probe.Close() + $up = $true + break + } + catch { + Start-Sleep -Milliseconds 500 + } + } + if (-not $up) { + throw "wolfsshd did not listen on the -p port $Port; -D did not honor -f/-p" + } + + # The config Port must not have been used: nothing should answer there. + $confBound = $false + try { + $probe = New-Object System.Net.Sockets.TcpClient + $probe.Connect("127.0.0.1", $ConfPort) + $probe.Close() + $confBound = $true + } + catch { + # expected: no listener on the config Port + } + if ($confBound) { + throw "wolfsshd listened on the config Port $ConfPort; -p override was not applied" + } + + Write-Host "PASS: -D mode parsed -f and -p (listening on $Port, not $ConfPort)" + $exitCode = 0 +} +catch { + Write-Host "FAIL: $_" + $exitCode = 1 +} +finally { + if ($sshd -and -not $sshd.HasExited) { + Stop-Process -Id $sshd.Id -Force -ErrorAction SilentlyContinue + } + Remove-Item -Path $confFile, $authFile -Force -ErrorAction SilentlyContinue +} + +exit $exitCode diff --git a/src/internal.c b/src/internal.c index 10769aef4..c63d54165 100644 --- a/src/internal.c +++ b/src/internal.c @@ -22616,6 +22616,9 @@ int wolfSSH_CleanPath(WOLFSSH* ssh, char* in, int inSz) byte found; char *path; void *heap = NULL; +#if defined(WOLFSSL_NUCLEUS) || defined(USE_WINDOWS_API) + int j; +#endif if (in == NULL || inSz <= 0) { return WS_BAD_ARGUMENT; @@ -22715,19 +22718,16 @@ int wolfSSH_CleanPath(WOLFSSH* ssh, char* in, int inSz) } /* clean up any multiple drive listed i.e. A:/A: */ - { - int i,j; - sz = (long)WSTRLEN(path); - for (i = 0, j = 0; i < sz; i++) { - if (path[i] == ':') { - if (j == 0) j = i; - else { - /* @TODO only checking once */ - WMEMMOVE(path, path + i - WS_DRIVE_SIZE, - sz - i + WS_DRIVE_SIZE); - path[sz - i + WS_DRIVE_SIZE] = '\0'; - break; - } + sz = (long)WSTRLEN(path); + for (i = 0, j = 0; i < sz; i++) { + if (path[i] == ':') { + if (j == 0) j = i; + else { + /* @TODO only checking once */ + WMEMMOVE(path, path + i - WS_DRIVE_SIZE, + sz - i + WS_DRIVE_SIZE); + path[sz - i + WS_DRIVE_SIZE] = '\0'; + break; } } } diff --git a/tests/regress.c b/tests/regress.c index eca4fd729..e0947c47f 100644 --- a/tests/regress.c +++ b/tests/regress.c @@ -32,7 +32,9 @@ #include #include -#include +#ifndef _WIN32 + #include +#endif #include #include #include diff --git a/wolfssh/test.h b/wolfssh/test.h index a03f43686..a258075cc 100644 --- a/wolfssh/test.h +++ b/wolfssh/test.h @@ -401,7 +401,7 @@ static INLINE int mygetopt(int argc, char** argv, const char* optstring) } -#ifdef USE_WINDOWS_API +#if defined(USE_WINDOWS_API) && defined(_MSC_VER) #pragma warning(push) #pragma warning(disable:4996) /* For Windows builds, disable compiler warnings for: @@ -563,7 +563,7 @@ static INLINE void build_addr(SOCKADDR_IN_T* addr, const char* peer, } #endif /* WOLFSSH_NUCLEUS */ -#ifdef USE_WINDOWS_API +#if defined(USE_WINDOWS_API) && defined(_MSC_VER) #pragma warning(pop) #endif