diff --git a/.github/workflows/sftp-test.yml b/.github/workflows/sftp-test.yml index caccbeff8..429d47ad6 100644 --- a/.github/workflows/sftp-test.yml +++ b/.github/workflows/sftp-test.yml @@ -42,7 +42,7 @@ jobs: id: cache-wolfssl with: path: build-dir/ - key: wolfssh-sftp-wolfssl-${{ matrix.wolfssl }}-${{ matrix.os }} + key: wolfssh-sftp-wolfssl-${{ matrix.wolfssl }}-${{ matrix.os }}-keygen lookup-only: true - name: Checkout, build, and install wolfssl @@ -52,7 +52,7 @@ jobs: repository: wolfssl/wolfssl ref: ${{ matrix.wolfssl }} path: wolfssl - configure: --enable-ssh + configure: --enable-ssh --enable-keygen check: false install: true @@ -73,7 +73,7 @@ jobs: uses: actions/cache@v4 with: path: build-dir/ - key: wolfssh-sftp-wolfssl-${{ matrix.wolfssl }}-${{ matrix.os }} + key: wolfssh-sftp-wolfssl-${{ matrix.wolfssl }}-${{ matrix.os }}-keygen fail-on-cache-miss: true - uses: actions/checkout@v4 @@ -124,13 +124,13 @@ jobs: expect eof EOF chmod +x /tmp/sftp_test.exp - + # Install expect sudo apt-get update && sudo apt-get install -y expect - + # Run the expect script /tmp/sftp_test.exp - + # Verify the files match echo "Verifying file integrity..." if cmp -s /tmp/test.dat /tmp/sftp_test_dir/test_received.dat; then @@ -139,3 +139,90 @@ jobs: echo "SFTP Test FAILED: Files do not match" exit 1 fi + + build_wolfssh_large_rw: + needs: + - build_wolfssl + - create_matrix + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest ] + wolfssl: ${{ fromJson(needs.create_matrix.outputs['versions']) }} + name: Test wolfsftp large RW (10MB chunks, 3GB file) + runs-on: ${{ matrix.os }} + timeout-minutes: 20 + steps: + - name: Checking cache for wolfssl + uses: actions/cache@v4 + with: + path: build-dir/ + key: wolfssh-sftp-wolfssl-${{ matrix.wolfssl }}-${{ matrix.os }}-keygen + fail-on-cache-miss: true + + - uses: actions/checkout@v4 + with: + path: wolfssh/ + + - name: autogen + working-directory: ./wolfssh/ + run: ./autogen.sh + + - name: configure + working-directory: ./wolfssh/ + run: | + ./configure --enable-all \ + LDFLAGS="-L${{ github.workspace }}/build-dir/lib" \ + CPPFLAGS="-I${{ github.workspace }}/build-dir/include -DWOLFSSH_NO_SFTP_TIMEOUT -DWOLFSSH_MAX_SFTP_RW=10485760 -DWOLFSSH_MAX_CHN_NAMESZ=4200" + + - name: make + working-directory: ./wolfssh/ + run: make + + - name: Create 3GB test file + working-directory: ./wolfssh/ + run: | + dd if=/dev/urandom of=seed.dat bs=1M count=10 + for i in $(seq 1 308); do cat seed.dat >> large_test.dat; done + rm seed.dat + sha256sum large_test.dat > large_test.dat.sha256 + echo "Created 3GB test file, SHA-256: $(cat large_test.dat.sha256)" + + - name: Start echoserver + working-directory: ./wolfssh/ + run: | + ./examples/echoserver/echoserver -N -1 -R /tmp/echoserver_ready -d "$(pwd)" & + echo $! > /tmp/echoserver.pid + for i in $(seq 1 30); do + [ -s /tmp/echoserver_ready ] && break + sleep 0.2 + done + if [ ! -s /tmp/echoserver_ready ]; then + echo "ERROR: echoserver failed to start" + exit 1 + fi + echo "Echoserver ready on port $(cat /tmp/echoserver_ready)" + + - name: SFTP get 3GB file with 10MB chunk size + working-directory: ./wolfssh/ + run: | + port=$(cat /tmp/echoserver_ready) + ./examples/sftpclient/wolfsftp -N -u jill -P upthehill -p "$port" \ + -G -l /tmp/large_test_copy.dat -r "$(pwd)/large_test.dat" + + - name: Verify file integrity + working-directory: ./wolfssh/ + run: | + expected=$(awk '{print $1}' large_test.dat.sha256) + actual=$(sha256sum /tmp/large_test_copy.dat | awk '{print $1}') + echo "Expected SHA-256: $expected" + echo "Actual SHA-256: $actual" + if [ "$expected" != "$actual" ]; then + echo "FAIL: SHA-256 mismatch" + exit 1 + fi + echo "PASS: 3GB SFTP transfer with WOLFSSH_MAX_SFTP_RW=10485760 succeeded" + + - name: Stop echoserver + if: always() + run: kill "$(cat /tmp/echoserver.pid)" 2>/dev/null || true diff --git a/.github/workflows/windows-sftp.yml b/.github/workflows/windows-sftp.yml new file mode 100644 index 000000000..92aafe4ff --- /dev/null +++ b/.github/workflows/windows-sftp.yml @@ -0,0 +1,432 @@ +name: Windows wolfsshd SFTP Test + +# This workflow tests wolfsshd and SFTP on Windows with: +# 1. Basic test: wolfsshd + SFTP client (pwd, ls, put/get small file) +# 2. Large file test: WOLFSSH_NO_SFTP_TIMEOUT, WOLFSSH_MAX_SFTP_RW=10485760, +# WOLFSSH_MAX_CHN_NAMESZ=4200 - get and put a 3GB file + +on: + push: + branches: [ '*' ] + pull_request: + branches: [ '*' ] + +env: + WOLFSSL_SOLUTION_FILE_PATH: wolfssl64.sln + SOLUTION_FILE_PATH: wolfssh.sln + USER_SETTINGS_H_NEW: wolfssh/ide/winvs/user_settings.h + USER_SETTINGS_H: wolfssl/IDE/WIN/user_settings.h + INCLUDE_DIR: wolfssh + WOLFSSL_BUILD_CONFIGURATION: Release + WOLFSSH_BUILD_CONFIGURATION: Release + BUILD_PLATFORM: x64 + TARGET_PLATFORM: 10 + TEST_PORT: 22222 + +jobs: + build: + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: + - test_type: basic + artifact_name: wolfssh-windows-build + - test_type: large_rw + artifact_name: wolfssh-windows-build-large-rw + + steps: + - uses: actions/checkout@v4 + with: + repository: wolfssl/wolfssl + path: wolfssl + + - uses: actions/checkout@v4 + with: + path: wolfssh + + - name: Add MSBuild to PATH + uses: microsoft/setup-msbuild@v1 + + - name: Update user_settings.h for wolfSSL build + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: | + sed -i 's/#if 0/#if 1/g' ${{env.USER_SETTINGS_H_NEW}} + cp ${{env.USER_SETTINGS_H_NEW}} ${{env.USER_SETTINGS_H}} + + - name: Restore wolfSSL NuGet packages + working-directory: ${{ github.workspace }}\wolfssl + run: nuget restore ${{env.WOLFSSL_SOLUTION_FILE_PATH}} + + - name: Build wolfssl library + working-directory: ${{ github.workspace }}\wolfssl + run: msbuild /m /p:PlatformToolset=v142 /p:Platform=${{env.BUILD_PLATFORM}} /p:Configuration=${{env.WOLFSSL_BUILD_CONFIGURATION}} /t:wolfssl ${{env.WOLFSSL_SOLUTION_FILE_PATH}} + + - name: Upload wolfSSL build artifacts + if: matrix.test_type == 'basic' + uses: actions/upload-artifact@v4 + with: + name: wolfssl-windows-build + path: | + wolfssl/IDE/WIN/${{env.WOLFSSL_BUILD_CONFIGURATION}}/${{env.BUILD_PLATFORM}}/** + wolfssl/IDE/WIN/${{env.WOLFSSL_BUILD_CONFIGURATION}}/** + wolfssl/${{env.WOLFSSL_BUILD_CONFIGURATION}}/${{env.BUILD_PLATFORM}}/** + wolfssl/${{env.WOLFSSL_BUILD_CONFIGURATION}}/** + + - name: Update user_settings.h for sshd and SFTP + working-directory: ${{env.GITHUB_WORKSPACE}} + shell: bash + run: | + # Enable SSHD, SFTP support (second #if 0 block) + sed -i 's/#if 0/#if 1/g' ${{env.USER_SETTINGS_H_NEW}} + cp ${{env.USER_SETTINGS_H_NEW}} ${{env.USER_SETTINGS_H}} + # For large_rw test: add SFTP large file defines + if [ "${{ matrix.test_type }}" = "large_rw" ]; then + echo "" >> ${{env.USER_SETTINGS_H_NEW}} + echo "/* SFTP large file test defines */" >> ${{env.USER_SETTINGS_H_NEW}} + echo "#define WOLFSSH_NO_SFTP_TIMEOUT" >> ${{env.USER_SETTINGS_H_NEW}} + echo "#define WOLFSSH_MAX_SFTP_RW 10485760" >> ${{env.USER_SETTINGS_H_NEW}} + echo "#define WOLFSSH_MAX_CHN_NAMESZ 4200" >> ${{env.USER_SETTINGS_H_NEW}} + echo "Added WOLFSSH_NO_SFTP_TIMEOUT, WOLFSSH_MAX_SFTP_RW=10485760, WOLFSSH_MAX_CHN_NAMESZ=4200" + cp ${{env.USER_SETTINGS_H_NEW}} ${{env.USER_SETTINGS_H}} + fi + + - name: Restore NuGet packages + working-directory: ${{ github.workspace }}\wolfssh\ide\winvs + run: nuget restore ${{env.SOLUTION_FILE_PATH}} + + - name: Build wolfssh + working-directory: ${{ github.workspace }}\wolfssh\ide\winvs + run: msbuild /m /p:PlatformToolset=v142 /p:Platform=${{env.BUILD_PLATFORM}} /p:WindowsTargetPlatformVersion=${{env.TARGET_PLATFORM}} /p:Configuration=${{env.WOLFSSH_BUILD_CONFIGURATION}} ${{env.SOLUTION_FILE_PATH}} + + - name: Upload wolfSSH build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact_name }} + if-no-files-found: error + path: | + wolfssh/ide/winvs/**/Release/** + + test: + needs: build + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: + - test_type: basic + artifact_name: wolfssh-windows-build + - test_type: large_rw + artifact_name: wolfssh-windows-build-large-rw + + steps: + - uses: actions/checkout@v4 + with: + path: wolfssh + + - name: Download wolfSSH build artifacts + uses: actions/download-artifact@v4 + with: + name: ${{ matrix.artifact_name }} + path: . + + - name: Download wolfSSL build artifacts + uses: actions/download-artifact@v4 + with: + name: wolfssl-windows-build + path: . + + - name: Create Windows user testuser and authorized_keys + working-directory: ${{ github.workspace }}\wolfssh + shell: pwsh + run: | + $homeDir = "C:\Users\testuser" + $sshDir = "$homeDir\.ssh" + $authKeysFile = "$sshDir\authorized_keys" + $pw = 'T3stP@ss!xY9' + + New-Item -ItemType Directory -Path $homeDir -Force | Out-Null + New-Item -ItemType Directory -Path $sshDir -Force | Out-Null + + $o = net user testuser $pw /add /homedir:$homeDir 2>&1 + if ($LASTEXITCODE -ne 0) { + if ($o -match "already exists") { + net user testuser /homedir:$homeDir 2>$null + } else { + Write-Host "net user failed: $o" + exit 1 + } + } + Add-Content -Path $env:GITHUB_ENV -Value "TESTUSER_PASSWORD=$pw" + + "" | Out-File -FilePath $authKeysFile -Encoding ASCII -NoNewline + icacls $authKeysFile /grant "testuser:R" /q + + # Grant testuser full control of their home directory. + # New-Item creates the directory owned by the runner account; Windows + # only sets correct user ACLs during the normal profile-creation flow. + # Without this, ImpersonateLoggedOnUser succeeds but CreateFile fails + # with ACCESS_DENIED when wolfsshd tries to write files as testuser. + icacls $homeDir /grant "testuser:(OI)(CI)F" /T /q + + $sid = (New-Object System.Security.Principal.NTAccount("testuser")).Translate([System.Security.Principal.SecurityIdentifier]).Value + $profKey = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$sid" + if (-not (Test-Path $profKey)) { New-Item -Path $profKey -Force | Out-Null } + Set-ItemProperty -Path $profKey -Name "ProfileImagePath" -Value $homeDir -Force + + - name: Create wolfSSHd config file + working-directory: ${{ github.workspace }}\wolfssh + shell: pwsh + run: | + $keyPath = Join-Path "${{ github.workspace }}" "wolfssh\keys\server-key.pem" + $keyPathFull = (Resolve-Path $keyPath -ErrorAction Stop) + $configContent = @" + Port ${{env.TEST_PORT}} + PasswordAuthentication yes + PermitRootLogin yes + HostKey $($keyPathFull.Path) + AuthorizedKeysFile C:\Users\testuser\.ssh\authorized_keys + "@ + $configContent | Out-File -FilePath sshd_config_test -Encoding ASCII + Get-Content sshd_config_test + + - name: Find wolfSSH executables + working-directory: ${{ github.workspace }}\wolfssh + shell: pwsh + run: | + $searchRoot = "${{ github.workspace }}" + $sshdExe = Get-ChildItem -Path $searchRoot -Recurse -Filter "wolfsshd.exe" -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -like "*Release*" } | Select-Object -First 1 + if ($sshdExe) { + Add-Content -Path $env:GITHUB_ENV -Value "SSHD_PATH=$($sshdExe.FullName)" + } else { + Write-Host "ERROR: wolfsshd.exe not found" + exit 1 + } + + $sftpExe = Get-ChildItem -Path $searchRoot -Recurse -Filter "wolfsftp.exe" -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -like "*Release*" } | Select-Object -First 1 + if (-not $sftpExe) { + $sftpExe = Get-ChildItem -Path $searchRoot -Recurse -Filter "wolfsftp-client.exe" -ErrorAction SilentlyContinue | + Where-Object { $_.FullName -like "*Release*" } | Select-Object -First 1 + } + if ($sftpExe) { + Add-Content -Path $env:GITHUB_ENV -Value "SFTP_PATH=$($sftpExe.FullName)" + } else { + Write-Host "ERROR: SFTP client exe not found" + exit 1 + } + + - name: Copy wolfSSL DLL to executable directory + working-directory: ${{ github.workspace }} + shell: pwsh + run: | + $sshdPath = $env:SSHD_PATH + $sshdDir = Split-Path -Parent $sshdPath + if (Test-Path (Join-Path $sshdDir "wolfssl.lib")) { exit 0 } + $wolfsslDll = Get-ChildItem -Path "${{ github.workspace }}\wolfssl" -Recurse -Filter "wolfssl.dll" -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($wolfsslDll) { + Copy-Item -Path $wolfsslDll.FullName -Destination (Join-Path $sshdDir "wolfssl.dll") -Force + } + + - name: Grant service access to config and keys + working-directory: ${{ github.workspace }}\wolfssh + shell: pwsh + run: | + icacls (Get-Location).Path /grant "NT AUTHORITY\SYSTEM:(OI)(CI)RX" /T /q + + - name: Start wolfSSHd as Windows service + working-directory: ${{ github.workspace }}\wolfssh + shell: pwsh + run: | + $sshdPath = $env:SSHD_PATH + $configPathFull = (Resolve-Path "sshd_config_test").Path + $serviceName = "wolfsshd" + + $existingService = Get-Service -Name $serviceName -ErrorAction SilentlyContinue + if ($existingService) { + if ($existingService.Status -eq 'Running') { Stop-Service -Name $serviceName -Force } + sc.exe delete $serviceName | Out-Null + Start-Sleep -Seconds 2 + } + + $binPath = "`"$sshdPath`" -f `"$configPathFull`" -p ${{env.TEST_PORT}}" + sc.exe create $serviceName binPath= $binPath + sc.exe start $serviceName + Start-Sleep -Seconds 5 + + $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue + if ($service.Status -ne 'Running') { + Write-Host "ERROR: Service failed to start" + sc.exe query $serviceName + exit 1 + } + Add-Content -Path $env:GITHUB_ENV -Value "SSHD_SERVICE_NAME=$serviceName" + + - name: Test SFTP get non-existent file (no hang, correct error) + working-directory: ${{ github.workspace }}\wolfssh + shell: pwsh + timeout-minutes: 1 + run: | + $sftpPath = $env:SFTP_PATH + $destFile = Join-Path $env:TEMP "copy.dat" + $getCommands = "get /this_file_does_not_exist_xyz $destFile`nquit" + $getCommands | Out-File -FilePath sftp_get_nonexistent_commands.txt -Encoding ASCII + + $proc = Start-Process -FilePath $sftpPath ` + -ArgumentList "-u", "testuser", "-P", $env:TESTUSER_PASSWORD, "-h", "localhost", "-p", "${{env.TEST_PORT}}" ` + -RedirectStandardInput "sftp_get_nonexistent_commands.txt" ` + -RedirectStandardOutput "sftp_get_nonexistent_out.txt" ` + -RedirectStandardError "sftp_get_nonexistent_err.txt" ` + -Wait -NoNewWindow -PassThru + + Write-Host "=== SFTP Output ===" + $output = "" + if (Test-Path sftp_get_nonexistent_out.txt) { + $output = Get-Content sftp_get_nonexistent_out.txt -Raw + Write-Host $output + } + Write-Host "=== SFTP Error ===" + if (Test-Path sftp_get_nonexistent_err.txt) { Get-Content sftp_get_nonexistent_err.txt } + + # Verify file was NOT created + if (Test-Path $destFile) { + Write-Host "ERROR: $destFile was created despite non-existent source file" + exit 1 + } + # Verify error message was emitted + if ($output -notmatch "Error getting file") { + Write-Host "ERROR: Expected 'Error getting file' in output" + exit 1 + } + Write-Host "PASS: SFTP get non-existent file did not create file, reported error correctly, did not hang" + + - name: Test SFTP connection (basic) + if: matrix.test_type == 'basic' + working-directory: ${{ github.workspace }}\wolfssh + shell: pwsh + run: | + $sftpPath = $env:SFTP_PATH + $testCommands = "pwd`nls`nquit" + $testCommands | Out-File -FilePath sftp_commands.txt -Encoding ASCII + + $process = Start-Process -FilePath $sftpPath ` + -ArgumentList "-u", "testuser", "-P", $env:TESTUSER_PASSWORD, "-h", "localhost", "-p", "${{env.TEST_PORT}}" ` + -RedirectStandardInput "sftp_commands.txt" ` + -RedirectStandardOutput "sftp_output.txt" ` + -RedirectStandardError "sftp_error.txt" ` + -Wait -NoNewWindow -PassThru + + Get-Content sftp_output.txt + Get-Content sftp_error.txt + if ($process.ExitCode -ne 0) { + Write-Host "ERROR: SFTP basic test failed with exit $($process.ExitCode)" + exit 1 + } + Write-Host "Basic SFTP test passed" + + - name: Create 3GB test file and run SFTP get/put + if: matrix.test_type == 'large_rw' + working-directory: ${{ github.workspace }}\wolfssh + shell: pwsh + timeout-minutes: 25 + run: | + $sftpPath = $env:SFTP_PATH + $workDir = Join-Path $env:GITHUB_WORKSPACE "wolfssh" + $largeFile = Join-Path $workDir "large_test.dat" + $getDestPath = Join-Path $workDir "large_test_copy.dat" + + # Create 3GB file: one random 10MB chunk repeated 307x + 2MB + Write-Host "Creating 3GB test file..." + $chunkSize = 10485760 # 10MB + $totalSize = [long]3221225472 # 3GB + $rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider + $chunk = New-Object byte[] $chunkSize + $rng.GetBytes($chunk) + $fs = [System.IO.File]::Create($largeFile) + $remaining = $totalSize + while ($remaining -gt 0) { + $toWrite = [int][Math]::Min([long]$chunkSize, $remaining) + $fs.Write($chunk, 0, $toWrite) + $remaining -= $toWrite + } + $fs.Close() + + $hash = Get-FileHash -Path $largeFile -Algorithm SHA256 + $hash.Hash | Out-File -FilePath (Join-Path $workDir "large_test.dat.sha256") + Write-Host "Created 3GB file, SHA256: $($hash.Hash)" + + # SFTP PUT (upload) + # Use a relative remote path (no leading /) so the server resolves it + # under testuser's home directory. An absolute /large_test.dat maps to + # the drive root (C:\) where testuser has no write permission; relative + # large_test.dat is prefixed by workingDir (C:\Users\testuser) on the + # client and becomes C:\Users\testuser\large_test.dat on the server. + Write-Host "SFTP PUT 3GB file..." + $putCommands = "put $largeFile large_test.dat`nquit" + $putCommands | Out-File -FilePath (Join-Path $workDir "sftp_put_commands.txt") -Encoding ASCII + $proc = Start-Process -FilePath $sftpPath ` + -ArgumentList "-u", "testuser", "-P", $env:TESTUSER_PASSWORD, "-h", "localhost", "-p", "${{env.TEST_PORT}}" ` + -WorkingDirectory $workDir ` + -RedirectStandardInput (Join-Path $workDir "sftp_put_commands.txt") ` + -RedirectStandardOutput (Join-Path $workDir "sftp_put_out.txt") ` + -RedirectStandardError (Join-Path $workDir "sftp_put_err.txt") ` + -Wait -NoNewWindow -PassThru + + $putOut = Get-Content (Join-Path $workDir "sftp_put_out.txt") -Raw -ErrorAction SilentlyContinue + Write-Host "=== SFTP PUT output ==="; Write-Host $putOut + if ($proc.ExitCode -ne 0 -or $putOut -match "Error pushing file") { + Get-Content (Join-Path $workDir "sftp_put_err.txt") -ErrorAction SilentlyContinue + Write-Host "ERROR: SFTP PUT failed" + exit 1 + } + Write-Host "PUT succeeded" + + # SFTP GET (download) - relative remote and local paths. + # Remote large_test.dat resolves to C:\Users\testuser\large_test.dat. + # Local large_test_copy.dat is relative to wolfsftp's CWD ($workDir). + Write-Host "SFTP GET 3GB file..." + $getCommands = "get large_test.dat large_test_copy.dat`nquit" + $getCommands | Out-File -FilePath (Join-Path $workDir "sftp_get_commands.txt") -Encoding ASCII + $proc2 = Start-Process -FilePath $sftpPath ` + -ArgumentList "-u", "testuser", "-P", $env:TESTUSER_PASSWORD, "-h", "localhost", "-p", "${{env.TEST_PORT}}" ` + -WorkingDirectory $workDir ` + -RedirectStandardInput (Join-Path $workDir "sftp_get_commands.txt") ` + -RedirectStandardOutput (Join-Path $workDir "sftp_get_out.txt") ` + -RedirectStandardError (Join-Path $workDir "sftp_get_err.txt") ` + -Wait -NoNewWindow -PassThru + + $getOut = Get-Content (Join-Path $workDir "sftp_get_out.txt") -Raw -ErrorAction SilentlyContinue + Write-Host "=== SFTP GET output ==="; Write-Host $getOut + if ($proc2.ExitCode -ne 0 -or $getOut -match "Error getting file") { + Get-Content (Join-Path $workDir "sftp_get_err.txt") -ErrorAction SilentlyContinue + Write-Host "ERROR: SFTP GET failed" + exit 1 + } + Write-Host "GET succeeded" + + # Verify integrity (file is in $workDir from GET with relative path) + $expectedHash = (Get-Content (Join-Path $workDir "large_test.dat.sha256")).Trim() + $actualHash = (Get-FileHash -Path $getDestPath -Algorithm SHA256).Hash + Write-Host "File: $getDestPath" + Write-Host "Expected SHA256: $expectedHash" + Write-Host "Actual SHA256: $actualHash" + if ($expectedHash -ne $actualHash) { + Write-Host "ERROR: SHA256 mismatch - PUT/GET corruption" + exit 1 + } + Write-Host "PASS: 3GB SFTP get/put succeeded" + + - name: Cleanup + if: always() + working-directory: ${{ github.workspace }}\wolfssh + shell: pwsh + run: | + $serviceName = $env:SSHD_SERVICE_NAME + if (-not $serviceName) { $serviceName = "wolfsshd" } + $service = Get-Service -Name $serviceName -ErrorAction SilentlyContinue + if ($service) { + if ($service.Status -eq 'Running') { Stop-Service -Name $serviceName -Force } + sc.exe delete $serviceName | Out-Null + } diff --git a/examples/sftpclient/sftpclient.c b/examples/sftpclient/sftpclient.c index 0b8139baa..b54a74579 100644 --- a/examples/sftpclient/sftpclient.c +++ b/examples/sftpclient/sftpclient.c @@ -154,6 +154,7 @@ static void myStatusCb(WOLFSSH* sshIn, word32* bytes, char* name) word32 currentTime; #ifndef WOLFSSH_NO_TIMESTAMP static word32 lastOutputTime = 0; + static word32 lastPrintedBytes[2] = {0, 0}; word32 elapsedTime; #endif char buf[80]; @@ -162,12 +163,24 @@ static void myStatusCb(WOLFSSH* sshIn, word32* bytes, char* name) #ifndef WOLFSSH_NO_TIMESTAMP currentTime = current_time(0); if (currentTime == lastOutputTime) { - return; + if (bytes[0] != lastPrintedBytes[0] || bytes[1] != lastPrintedBytes[1]) { + /* Progress made in the same second — throttle but track latest */ + lastPrintedBytes[0] = bytes[0]; + lastPrintedBytes[1] = bytes[1]; + return; + } + /* bytes unchanged: EOF final call — fall through to print */ + } + else { + lastOutputTime = currentTime; } - lastOutputTime = currentTime; + lastPrintedBytes[0] = bytes[0]; + lastPrintedBytes[1] = bytes[1]; if (WSTRNCMP(currentFile, name, WSTRLEN(name)) != 0) { startTime = current_time(1); lastOutputTime = 0; /* Reset timer for new file transfer */ + lastPrintedBytes[0] = 0; + lastPrintedBytes[1] = 0; WMEMSET(currentFile, 0, WOLFSSH_MAX_FILENAME); WSTRNCPY(currentFile, name, WOLFSSH_MAX_FILENAME); } @@ -756,8 +769,8 @@ static int doCmds(func_args* args) ret = wolfSSH_SFTP_STAT(ssh, pt, &atrb); err = wolfSSH_get_error(ssh); - } while ((err == WS_WANT_READ || err == WS_WANT_WRITE) - && ret != WS_SUCCESS); + } while ((err == WS_WANT_READ || err == WS_WANT_WRITE || + err == WS_REKEYING) && ret != WS_SUCCESS); if (ret != WS_SUCCESS) { if (SFTP_FPUTS(args, "Error changing directory\n") < 0) { err_msg("fputs error"); @@ -844,8 +857,8 @@ static int doCmds(func_args* args) ret = wolfSSH_SFTP_CHMOD(ssh, pt, mode); err = wolfSSH_get_error(ssh); - } while ((err == WS_WANT_READ || err == WS_WANT_WRITE) - && ret != WS_SUCCESS); + } while ((err == WS_WANT_READ || err == WS_WANT_WRITE || + err == WS_REKEYING) && ret != WS_SUCCESS); if (ret != WS_SUCCESS) { if (SFTP_FPUTS(args, "Unable to change permissions of ") < 0) { err_msg("fputs error"); @@ -901,8 +914,8 @@ static int doCmds(func_args* args) ret = wolfSSH_SFTP_RMDIR(ssh, pt); err = wolfSSH_get_error(ssh); - } while ((err == WS_WANT_READ || err == WS_WANT_WRITE) - && ret != WS_SUCCESS); + } while ((err == WS_WANT_READ || err == WS_WANT_WRITE || + err == WS_REKEYING) && ret != WS_SUCCESS); if (ret != WS_SUCCESS) { if (ret == WS_PERMISSIONS) { if (SFTP_FPUTS(args, "Insufficient permissions\n") < 0) { @@ -954,8 +967,8 @@ static int doCmds(func_args* args) ret = wolfSSH_SFTP_Remove(ssh, pt); err = wolfSSH_get_error(ssh); - } while ((err == WS_WANT_READ || err == WS_WANT_WRITE) - && ret != WS_SUCCESS); + } while ((err == WS_WANT_READ || err == WS_WANT_WRITE || + err == WS_REKEYING) && ret != WS_SUCCESS); if (ret != WS_SUCCESS) { if (ret == WS_PERMISSIONS) { if (SFTP_FPUTS(args, "Insufficient permissions\n") < 0) { diff --git a/src/wolfsftp.c b/src/wolfsftp.c index 90b31c944..ebec0fae8 100644 --- a/src/wolfsftp.c +++ b/src/wolfsftp.c @@ -6435,7 +6435,7 @@ static WS_SFTPNAME* wolfSSH_SFTP_DoName(WOLFSSH* ssh) ret = wolfSSH_SFTP_buffer_read(ssh, &state->buffer, wolfSSH_SFTP_buffer_size(&state->buffer)); if (ret < 0) { - if (ssh->error != WS_WANT_READ) { + if (!NoticeError(ssh)) { wolfSSH_SFTP_ClearState(ssh, STATE_ID_NAME); } return NULL; @@ -6443,7 +6443,7 @@ static WS_SFTPNAME* wolfSSH_SFTP_DoName(WOLFSSH* ssh) wolfSSH_SFTP_buffer_rewind(&state->buffer); wolfSSH_SFTP_DoStatus(ssh, reqId, &state->buffer); - if (ssh->error != WS_WANT_READ) { + if (!NoticeError(ssh)) { wolfSSH_SFTP_ClearState(ssh, STATE_ID_NAME); } return NULL; @@ -6458,7 +6458,7 @@ static WS_SFTPNAME* wolfSSH_SFTP_DoName(WOLFSSH* ssh) wolfSSH_SFTP_buffer_size(&state->buffer) - wolfSSH_SFTP_buffer_idx(&state->buffer)); if (ret <= 0) { - if (ssh->error != WS_WANT_READ) { + if (!NoticeError(ssh)) { wolfSSH_SFTP_ClearState(ssh, STATE_ID_NAME); } return NULL; @@ -6909,8 +6909,7 @@ int wolfSSH_SFTP_CHMOD(WOLFSSH* ssh, char* n, char* oct) case STATE_CHMOD_GET: /* get current attributes of path */ if ((ret = wolfSSH_SFTP_STAT(ssh, n, &state->atr)) != WS_SUCCESS) { - if (ssh->error != WS_WANT_READ - && ssh->error != WS_WANT_WRITE) { + if (!NoticeError(ssh)) { break; } return ret; @@ -6990,14 +6989,12 @@ static int SFTP_STAT(WOLFSSH* ssh, char* dir, WS_SFTP_FILEATRB* atr, byte type) WLOG(WS_LOG_SFTP, "SFTP LSTAT STATE: SEND_TYPE_REQ"); ret = SendPacketType(ssh, type, (byte*)dir, state->dirSz); if (ret != WS_SUCCESS) { - if (ssh->error == WS_WANT_READ || - ssh->error == WS_WANT_WRITE) + if (NoticeError(ssh)) { return WS_FATAL_ERROR; - else { - ret_fatal = 1; - state->state = STATE_LSTAT_CLEANUP; - continue; } + ret_fatal = 1; + state->state = STATE_LSTAT_CLEANUP; + continue; } state->state = STATE_LSTAT_GET_HEADER; FALL_THROUGH; @@ -7008,14 +7005,12 @@ static int SFTP_STAT(WOLFSSH* ssh, char* dir, WS_SFTP_FILEATRB* atr, byte type) ret = SFTP_GetHeader(ssh, &state->reqId, &state->type, &state->buffer); if (ret <= 0) { - if (ssh->error == WS_WANT_READ || - ssh->error == WS_WANT_WRITE) + if (NoticeError(ssh)) { return WS_FATAL_ERROR; - else { - state->state = STATE_LSTAT_CLEANUP; - ret_fatal = 1; - continue; } + state->state = STATE_LSTAT_CLEANUP; + ret_fatal = 1; + continue; } state->state = STATE_LSTAT_CHECK_REQ_ID; @@ -7042,7 +7037,7 @@ static int SFTP_STAT(WOLFSSH* ssh, char* dir, WS_SFTP_FILEATRB* atr, byte type) ret = wolfSSH_SFTP_buffer_read(ssh, &state->buffer, wolfSSH_SFTP_buffer_size(&state->buffer)); if (ret < 0) { - if (ssh->error != WS_WANT_READ) { + if (!NoticeError(ssh)) { wolfSSH_SFTP_ClearState(ssh, STATE_ID_LSTAT); } return WS_FATAL_ERROR; @@ -7211,8 +7206,7 @@ int wolfSSH_SFTP_SetSTAT(WOLFSSH* ssh, char* dir, WS_SFTP_FILEATRB* atr) /* send header and type specific data */ case STATE_SET_ATR_SEND: if (wolfSSH_SFTP_buffer_send(ssh, &state->buffer) < 0) { - if (ssh->error != WS_WANT_READ - && ssh->error != WS_WANT_WRITE) { + if (!NoticeError(ssh)) { ret = WS_FATAL_ERROR; break; } @@ -7229,8 +7223,7 @@ int wolfSSH_SFTP_SetSTAT(WOLFSSH* ssh, char* dir, WS_SFTP_FILEATRB* atr) case STATE_SET_ATR_GET: maxSz = SFTP_GetHeader(ssh, &state->reqId, &type, &state->buffer); if (maxSz <= 0) { - if (ssh->error != WS_WANT_READ - && ssh->error != WS_WANT_WRITE) { + if (!NoticeError(ssh)) { ret = WS_FATAL_ERROR; break; } @@ -7257,8 +7250,7 @@ int wolfSSH_SFTP_SetSTAT(WOLFSSH* ssh, char* dir, WS_SFTP_FILEATRB* atr) ret = wolfSSH_SFTP_buffer_read(ssh, &state->buffer, wolfSSH_SFTP_buffer_size(&state->buffer)); if (ret < 0) { - if (ssh->error != WS_WANT_READ - && ssh->error != WS_WANT_WRITE) { + if (!NoticeError(ssh)) { ret = WS_FATAL_ERROR; break; } @@ -7730,12 +7722,8 @@ int wolfSSH_SFTP_SendReadPacket(WOLFSSH* ssh, byte* handle, word32 handleSz, if (NoticeError(ssh)) { return WS_FATAL_ERROR; } - if (ssh->error != WS_WANT_READ && - ssh->error != WS_WANT_WRITE) { - state->state = STATE_SEND_READ_CLEANUP; - continue; - } - return ret; + state->state = STATE_SEND_READ_CLEANUP; + continue; } wolfSSH_SFTP_buffer_free(ssh, &state->buffer); state->state = STATE_SEND_READ_GET_HEADER; @@ -7746,11 +7734,11 @@ int wolfSSH_SFTP_SendReadPacket(WOLFSSH* ssh, byte* handle, word32 handleSz, /* Get response */ if ((ret = SFTP_GetHeader(ssh, &state->reqId, &state->type, &state->buffer)) <= 0) { - if (!NoticeError(ssh)) { - state->state = STATE_SEND_READ_CLEANUP; - continue; + if (NoticeError(ssh)) { + return WS_FATAL_ERROR; } - return WS_FATAL_ERROR; + state->state = STATE_SEND_READ_CLEANUP; + continue; } ret = wolfSSH_SFTP_buffer_create(ssh, &state->buffer, ret); if (ret != WS_SUCCESS) { @@ -7789,11 +7777,11 @@ int wolfSSH_SFTP_SendReadPacket(WOLFSSH* ssh, byte* handle, word32 handleSz, /* get size of string and place it into out buffer */ ret = wolfSSH_stream_read(ssh, szFlat, UINT32_SZ); if (ret < 0) { - if (!NoticeError(ssh)) { - state->state = STATE_SEND_READ_CLEANUP; - continue; + if (NoticeError(ssh)) { + return WS_FATAL_ERROR; } - return ret; + state->state = STATE_SEND_READ_CLEANUP; + continue; } ato32(szFlat, &sz); wolfSSH_SFTP_buffer_create(ssh, &state->buffer, sz); @@ -7814,8 +7802,7 @@ int wolfSSH_SFTP_SendReadPacket(WOLFSSH* ssh, byte* handle, word32 handleSz, out + state->recvSz, wolfSSH_SFTP_buffer_size(&state->buffer)); if (ret < 0) { - if (ssh->error == WS_WANT_READ || - ssh->error == WS_WANT_WRITE) { + if (NoticeError(ssh)) { return WS_FATAL_ERROR; } WLOG(WS_LOG_SFTP, "Error reading remainder of data"); @@ -7852,12 +7839,11 @@ int wolfSSH_SFTP_SendReadPacket(WOLFSSH* ssh, byte* handle, word32 handleSz, ret = wolfSSH_SFTP_buffer_read(ssh, &state->buffer, wolfSSH_SFTP_buffer_size(&state->buffer)); if (ret < 0) { - if (ssh->error != WS_WANT_READ && - ssh->error != WS_WANT_WRITE) { - state->state = STATE_SEND_READ_CLEANUP; - continue; + if (NoticeError(ssh)) { + return WS_FATAL_ERROR; } - return WS_FATAL_ERROR; + state->state = STATE_SEND_READ_CLEANUP; + continue; } wolfSSH_SFTP_buffer_rewind(&state->buffer); ret = wolfSSH_SFTP_DoStatus(ssh, @@ -8176,8 +8162,7 @@ int wolfSSH_SFTP_Close(WOLFSSH* ssh, byte* handle, word32 handleSz) ret = wolfSSH_SFTP_buffer_read(ssh, &state->buffer, wolfSSH_SFTP_buffer_size(&state->buffer)); if (ret < 0) { - if (ssh->error != WS_WANT_WRITE && - ssh->error != WS_WANT_READ) { + if (!NoticeError(ssh)) { wolfSSH_SFTP_buffer_free(ssh, &state->buffer); } return WS_FATAL_ERROR; @@ -8523,8 +8508,7 @@ int wolfSSH_SFTP_Remove(WOLFSSH* ssh, char* f) case STATE_RM_LSTAT: /* check file is there to be removed */ if ((ret = wolfSSH_SFTP_LSTAT(ssh, f, &atrb)) != WS_SUCCESS) { - if (ssh->error != WS_WANT_WRITE - && ssh->error != WS_WANT_READ) { + if (!NoticeError(ssh)) { WLOG(WS_LOG_SFTP, "Error verifying file"); wolfSSH_SFTP_ClearState(ssh, STATE_ID_RM); } @@ -8537,8 +8521,7 @@ int wolfSSH_SFTP_Remove(WOLFSSH* ssh, char* f) ret = SendPacketType(ssh, WOLFSSH_FTP_REMOVE, (byte*)f, (word32)WSTRLEN(f)); if (ret != WS_SUCCESS) { - if (ssh->error != WS_WANT_WRITE - && ssh->error != WS_WANT_READ) { + if (!NoticeError(ssh)) { wolfSSH_SFTP_ClearState(ssh, STATE_ID_RM); } return ret; @@ -8549,8 +8532,7 @@ int wolfSSH_SFTP_Remove(WOLFSSH* ssh, char* f) case STATE_RM_GET: ret = SFTP_GetHeader(ssh, &state->reqId, &type, &state->buffer); if (ret <= 0 || type != WOLFSSH_FTP_STATUS) { - if (ssh->error != WS_WANT_WRITE - && ssh->error != WS_WANT_READ) { + if (!NoticeError(ssh)) { WLOG(WS_LOG_SFTP, "Unexpected packet type"); wolfSSH_SFTP_ClearState(ssh, STATE_ID_RM); } @@ -8568,8 +8550,7 @@ int wolfSSH_SFTP_Remove(WOLFSSH* ssh, char* f) ret = wolfSSH_SFTP_buffer_read(ssh, &state->buffer, wolfSSH_SFTP_buffer_size(&state->buffer)); if (ret < 0) { - if (ssh->error != WS_WANT_WRITE - && ssh->error != WS_WANT_READ) { + if (!NoticeError(ssh)) { WLOG(WS_LOG_SFTP, "Unexpected packet type"); wolfSSH_SFTP_ClearState(ssh, STATE_ID_RM); } @@ -8630,8 +8611,7 @@ int wolfSSH_SFTP_RMDIR(WOLFSSH* ssh, char* dir) ret = SendPacketType(ssh, WOLFSSH_FTP_RMDIR, (byte*)dir, (word32)WSTRLEN(dir)); if (ret != WS_SUCCESS) { - if (ssh->error != WS_WANT_READ - && ssh->error != WS_WANT_WRITE) { + if (!NoticeError(ssh)) { wolfSSH_SFTP_ClearState(ssh, STATE_ID_RMDIR); } return ret; @@ -8642,7 +8622,7 @@ int wolfSSH_SFTP_RMDIR(WOLFSSH* ssh, char* dir) case STATE_RMDIR_GET: ret = SFTP_GetHeader(ssh, &state->reqId, &type, &state->buffer); if (ret <= 0 || type != WOLFSSH_FTP_STATUS) { - if (ssh->error != WS_WANT_READ) { + if (!NoticeError(ssh)) { wolfSSH_SFTP_ClearState(ssh, STATE_ID_RMDIR); WLOG(WS_LOG_SFTP, "Unexpected packet type"); } @@ -8659,7 +8639,7 @@ int wolfSSH_SFTP_RMDIR(WOLFSSH* ssh, char* dir) ret = wolfSSH_SFTP_buffer_read(ssh, &state->buffer, wolfSSH_SFTP_buffer_size(&state->buffer)); if (ret < 0) { - if (ssh->error != WS_WANT_READ) + if (!NoticeError(ssh)) wolfSSH_SFTP_ClearState(ssh, STATE_ID_RMDIR); return WS_FATAL_ERROR; } @@ -8867,9 +8847,9 @@ int wolfSSH_SFTP_Get(WOLFSSH* ssh, char* from, WLOG(WS_LOG_SFTP, "SFTP GET STATE: STAT"); ret = wolfSSH_SFTP_STAT(ssh, from, &state->attrib); if (ret != WS_SUCCESS) { - if (ssh->error == WS_WANT_READ || - ssh->error == WS_WANT_WRITE) + if (NoticeError(ssh)) { return WS_FATAL_ERROR; + } WLOG(WS_LOG_SFTP, "Error verifying file"); state->state = STATE_GET_CLEANUP; continue; @@ -8894,8 +8874,7 @@ int wolfSSH_SFTP_Get(WOLFSSH* ssh, char* from, ret = wolfSSH_SFTP_Open(ssh, from, WOLFSSH_FXF_READ, NULL, state->handle, &state->handleSz); if (ret != WS_SUCCESS) { - if (ssh->error == WS_WANT_READ || - ssh->error == WS_WANT_WRITE) { + if (NoticeError(ssh)) { return WS_FATAL_ERROR; } WLOG(WS_LOG_SFTP, "Error getting handle");