Release 2023.0.0 (#1201)

* Assets/logos (#782)

* Added logo assets

* Added PNG 1260x640 with white border

Co-authored-by: 103filgualan <f.gualandi@crif.com>

* OPENSSH KeyReader for more keys (#614)

* OPENSSH KeyReader for more keys

Add support to parse OpenSSH Keys with ECDSA 256/384/521 and RSA.

https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key

Change-Id: Iaa9cce0f2522e5fee377a82cb252f81f0b7cc563

* Fix ED25519Key KeyLength

* Fix ED25519 PubKey-auth

LeadingZeros of BigInteger-Conversion have to be removed
before sending the Key.

* Add interface to SftpFile #120 (#812)

* Create ISftpFile interface. SftpFile sealed. Return ISftpFile from SftpClient instead of SftpFile. Make ISftpClient interface disposable.

Co-authored-by: Wojciech Swieboda <wswieboda@chathamfinancial.com>

* Start MessageListener with ThreadAbstraction.ExecuteThreadLongRunning (#902)

* Fix Thread pool exhaustion due to MessageListener running on ThreadPool
* Mark long running thread as background

* Add async support to SftpClient and SftpFileStream (#819)

* Add FEATURE_TAP and net472 target
* Add TAP async support to SftpClient and SftpFileStream
* Add async support to DnsAbstraction and SocketAbstraction
* Add async support to *Connector and refactor the hierarchy
* Add ConnectAsync to BaseClient

* Add CODEOWNERS file.

* Fix virus false-positive by Defender on Renci.SSHNet.Tests.dll (#867)

Co-authored-by: Pedro Fonseca <pfonseca@qti.qualcomm.com>

* Add unit tests for task-based asynchronous API (#906)

* Fix runtime and culture dependant tests.
* Set C# 7.3 in Tests.csproj to limit intellisense's suggestions under different targets
* Add SftpClientTest.*Async
* Add SftpFileStreamTest_OpenAsync_*
* Add SftpFileStreamTest_WriteAsync_*
* Add SftpFileStreamTest_ReadAsync_*
* Align AppVeyor script with Test project target frameworks

* correct 'Documenation' to 'Documentation' (#838)

in the documentation's window title

* Agent auth and Keygen (#794)

* Allow to set PrivateKeyFile Key directly
   So you can add your own Key-Classes to SSH.NET
* Add ED25519 ctor for just pub key part.
* Make ECDSA Key Bits accessible
   You cant export imported CngKeys. To be able to export them to agent or Key-Files make the private bits also accessible.
* Better NETFRAMEWORK vs NETSTANDARD handling
* Add Comment Property to Key
* Add IPrivateKeySource
  So Extension can add own PrivateKeyFiles, e.g. PuttyKeyFile.

* Use cryptographically secure random number generator.
Fixes CVE-2022-29245.

* Remove unused import.

* Add IBaseClient for BaseClient and ISftpClient to inherit from (#975)

Add IBaseClient for BaseClient and ISftpClient to inherit from

* fix typo (#999)

* Fix Seek Operations in SftpFileStream (#910)

* Fix offset operations in SftpFileStream.Seek
* Fix seek exception message and add default case for invalid seek origin
* Use named params when throwing ArgumentException
* Add tests for seeking from end of file

* Add back copyright to license. (#1060)

Fixes #1059.

* Removing old target frameworks (#1109)

Remove support for legacy / deprecated target frameworks while adding support for .NET 6.0 (and higher).
The supported target frameworks are now:
* .NETFramework 4.6.2 (and higher)
* .NET Standard 2.0
* .NET 6.0 (and higher)

* Remove old features [Part 1] (#1117)

Remove obsolete feature switches (now that we've remove support for legacy target frameworks) and remove corresponding conditional code.

* Remove FEATURE_DIRECTORYINFO_ENUMERATEFILES (#1119)

* Remove FEATURE_DIRECTORYINFO_ENUMERATEFILES
* Add exception documentation

* Fix some (lots of) issues reported by analyzers. (#1125)

Fix some (lots of) issues reported by analyzers.

* Round 2 of analyzer fixes and general cleanup. (#1132)

* Analyzer fixes round 3. (#1135)

* Replace Array<T>.Empty with Array.Empty<T>() (#1137)

* Replace IsNullOrWhiteSpace extension (#1142)

* Use License Expression for NuGet Package

licenseUrl is deprecated, see https://github.com/NuGet/Announcements/issues/32

* Integration tests

* Remove todos

* Update CODEOWNERS

* Use correct SSH.NET

* ListDirectoryAsync return IAsyncEnumerable (#1126)

* ListDirectoryAsync return IAsyncEnumerable

* Fix documentation

* Update README.md

* Fix

* Add Sftp ListDirectoryAsync test

* Revert

* Integration tests for ListDirectoryAsync with IAsyncEnumerable

* Fix the assembly resolution build warning (#1165)

* Delete performance/longrunning tests (#1143)

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Move Integration tests (#1173)

* Renci.SshNet.IntegrationTests

* Renci.SshNet.TestTools.OpenSSH

* Move integration tests to main repo

* Move old tests to new integration tests

* Move old integration tests to new integration tests

* Move more tests

* Move authentication tests

* Move SshClientTests

* Fix some tests

* Remove duplicated test

* Poc of ProcessDisruptor

* Rename

* Some fixes

* Remove performance tests

* Small improvements

* Add a benchmarks project (#1151)

* Add a benchmarks project

* Small improvements

---------

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Use ExceptionDispatchInfo to retain call stack in Session.WaitOnHandle() (#936)

* Use ExceptionDispatchInfo to retain call stack in Session.WaitOnHandle()

* merge

* Update src/Renci.SshNet/Session.cs

Co-authored-by: Rob Hague <rob.hague00@gmail.com>

---------

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>
Co-authored-by: Rob Hague <rob.hague00@gmail.com>

* Support SHA256 fingerprints for host key validation (#1098)

* Add tests for HostKeyEventArgs

* Add SHA256 fingerprint support

* Add support for RSA SHA-2 public key algorithms (#1177)

* Abstract out the hash algorithm from RsaDigitalSignature

* Add integration tests

* Add DigitalSignature property to KeyHostAlgorithm

* Add IHostAlgorithmsProvider interface

* Verify the host signature

* Fix HostKeyEventArgsTest after merge

* Remove PubkeyAcceptedAlgorithms ssh-rsa

* Add test coverage for RSA keys in PrivateKeyFile

* Obsolete IPrivateKeySource

---------

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Improvements after #1177 (#1180)

* Use ExceptionDispatchInfo in more places (#1182)

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Try to "fix" the flaky test (#1185)

* Enable DSA tests (#1181)

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* FingerPrints (#1186)

* Use OS-agnostic socket error codes to allow tests run on different OSes (#1179)

SocketErrorCode is OS agnostic, ErrorCode is OS specific.

On Windows ErrorCode = (int) SocketErrorCode, but on Mac and Unix it is not.

For example ExitCode for HostNotFound (11001) on Windows is 11001, on Mac & Unix is -131073. So testing for ExitCode == 11001 fails on Mac & Unix.

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Fix for channel session semaphore from thread blocking (#1071)

* Merging fix from @clivetong into our own SSH.NET fork
- The following article describes some of the issues with the double check lock that we have seen issues with: https://www.sudhanshutheone.com/posts/double-check-lock-csharp

* Merging fix from @clivetong into our own SSH.NET fork
- The following article describes some of the issues with the double check lock that we have seen issues with: https://www.sudhanshutheone.com/posts/double-check-lock-csharp

* Update Channel to fix AppVeyor failure (field should be readonly)

* Update ISftpClient for #120 (#1193)

* Implement set last write and access time (#1194)

* Add/migrate hmac+cipher integration tests (#1189)

* Add/migrate hmac+cipher integration tests

* fix integration tests

---------

Co-authored-by: Wojciech Nagórski <wojtpl2@gmail.com>

* Update tests for SetLastAccessTime(Utc) to also verify the time component and the Kind of the DateTime value returned by GetLastAccessTime(Utc). (#1198)

---------

Co-authored-by: Filippo Gualandi <filippo.gualandi@gmail.com>
Co-authored-by: 103filgualan <f.gualandi@crif.com>
Co-authored-by: Stefan Rinkes <darinkes@users.noreply.github.com>
Co-authored-by: wxtsxt <wojciech.swieboda@gmail.com>
Co-authored-by: Wojciech Swieboda <wswieboda@chathamfinancial.com>
Co-authored-by: Igor Milavec <igor.milavec@gmail.com>
Co-authored-by: drieseng <gert.driesen@telenet.be>
Co-authored-by: Pedro Fonseca <pbfonseca@gmail.com>
Co-authored-by: Pedro Fonseca <pfonseca@qti.qualcomm.com>
Co-authored-by: Maximiliano Jabase <maxijabase@gmail.com>
Co-authored-by: Owen Krueger <37021716+Owen-Krueger@users.noreply.github.com>
Co-authored-by: Masuri <psh0258@gmail.com>
Co-authored-by: LemonPi314 <49930425+LemonPi314@users.noreply.github.com>
Co-authored-by: Gert Driesen <gertdriesen@msn.com>
Co-authored-by: Rob Hague <rob.hague00@gmail.com>
Co-authored-by: Rob Hague <rh@johnstreetcapital.com>
Co-authored-by: Marius Thesing <marius.thesing@gmail.com>
Co-authored-by: Dāvis Mošenkovs <davikovs@gmail.com>
Co-authored-by: Dmitry Tsarevich <dimhotepus@users.noreply.github.com>
Co-authored-by: Patrick Yates <114094360+patrick-yates-redgate@users.noreply.github.com>
This commit is contained in:
Wojciech Nagórski
2023-10-10 21:32:33 +02:00
committed by GitHub
parent 4cdedf6524
commit 2bbfee4a37
779 changed files with 36305 additions and 22412 deletions
+1246
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -22,3 +22,6 @@ project.lock.json
# Build outputs
build/target/
# Benchmark results
BenchmarkDotNet.Artifacts/
+1
View File
@@ -0,0 +1 @@
* @drieseng @WojciechNagorski
+44
View File
@@ -0,0 +1,44 @@
<Project>
<Import Project="$(MSBuildThisFileFullPath).user" Condition="Exists('$(MSBuildThisFileFullPath).user')" />
<!--
Assembly Info properties that apply to all projects/assemblies.
-->
<PropertyGroup>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)src\Renci.SshNet.snk</AssemblyOriginatorKeyFile>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>latest</LangVersion>
<WarningLevel>9999</WarningLevel>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects>
</PropertyGroup>
<!--
Code analysis properties.
-->
<PropertyGroup>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<AnalysisLevel>preview-All</AnalysisLevel>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<!--
Add the stylecop config to each project.
-->
<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json" Link="stylecop.json" />
</ItemGroup>
<!--
Use fixed version of analyzers.
-->
<ItemGroup>
<!--
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0-preview1.23165.1" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="all" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.54" PrivateAssets="all" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.55.0.65544" PrivateAssets="all" />
-->
</ItemGroup>
</Project>
+2
View File
@@ -1,5 +1,7 @@
The MIT License (MIT)
Copyright (c) Renci, Oleg Kapeljushnik, Gert Driesen and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
+8 -39
View File
@@ -80,7 +80,7 @@ the missing test once you figure things out. 🤓
* RSA in OpenSSL PEM and ssh.com format
* DSA in OpenSSL PEM and ssh.com format
* ECDSA 256/384/521 in OpenSSL PEM format
* ED25519 in OpenSSH key format
* ECDSA 256/384/521, ED25519 and RSA in OpenSSH key format
Private keys can be encrypted using one of the following cipher methods:
* DES-EDE3-CBC
@@ -97,6 +97,8 @@ Private keys can be encrypted using one of the following cipher methods:
* ecdsa-sha2-nistp256
* ecdsa-sha2-nistp384
* ecdsa-sha2-nistp521
* rsa-sha2-512
* rsa-sha2-256
* ssh-rsa
* ssh-dss
@@ -116,15 +118,9 @@ Private keys can be encrypted using one of the following cipher methods:
## Framework Support
**SSH.NET** supports the following target frameworks:
* .NET Framework 3.5
* .NET Framework 4.0 (and higher)
* .NET Standard 1.3
* .NET Standard 2.0
* Silverlight 4
* Silverlight 5
* Windows Phone 7.1
* Windows Phone 8.0
* Universal Windows Platform 10
* .NETFramework 4.6.2 (and higher)
* .NET Standard 2.0 and 2.1
* .NET 6 (and higher)
## Usage
@@ -149,45 +145,18 @@ using (var client = new SftpClient(connectionInfo))
Establish a SSH connection using user name and password, and reject the connection if the fingerprint of the server does not match the expected fingerprint:
```cs
byte[] expectedFingerPrint = new byte[] {
0x66, 0x31, 0xaf, 0x00, 0x54, 0xb9, 0x87, 0x31,
0xff, 0x58, 0x1c, 0x31, 0xb1, 0xa2, 0x4c, 0x6b
};
string expectedFingerPrint = "LKOy5LvmtEe17S4lyxVXqvs7uPMy+yF79MQpHeCs/Qo";
using (var client = new SshClient("sftp.foo.com", "guest", "pwd"))
{
client.HostKeyReceived += (sender, e) =>
{
if (expectedFingerPrint.Length == e.FingerPrint.Length)
{
for (var i = 0; i < expectedFingerPrint.Length; i++)
{
if (expectedFingerPrint[i] != e.FingerPrint[i])
{
e.CanTrust = false;
break;
}
}
}
else
{
e.CanTrust = false;
}
e.CanTrust = expectedFingerPrint.Equals(e.FingerPrintSHA256);
};
client.Connect();
}
```
## Building SSH.NET
Software | net35 | net40 | netstandard1.3 | netstandard2.0 | sl4 | sl5 | wp71 | wp8 | uap10.0 |
--------------------------------- | :---: | :---: | :------------: | :------------: | :-: | :-: | :--: | :-: | :-----: |
Windows Phone SDK 8.0 | | | | | x | x | x | x |
Visual Studio 2012 Update 5 | x | x | | | x | x | x | x |
Visual Studio 2015 Update 3 | x | x | | | | x | | x | x
Visual Studio 2017 | x | x | x | x | | | | |
Visual Studio 2019 | x | x | x | x | | | | |
## Supporting SSH.NET
Do you or your company rely on **SSH.NET** in your projects? If you want to encourage us to keep on going and show us that you appreciate our work, please consider becoming a [sponsor](https://github.com/sponsors/sshnet) through GitHub Sponsors.
+6 -6
View File
@@ -1,14 +1,14 @@
os: Visual Studio 2019
os: Visual Studio 2022
before_build:
- nuget restore src\Renci.SshNet.VS2019.sln
- nuget restore src\Renci.SshNet.sln
build:
project: src\Renci.SshNet.VS2019.sln
project: src\Renci.SshNet.sln
verbosity: minimal
test_script:
- cmd: >-
vstest.console /logger:Appveyor src\Renci.SshNet.Tests\bin\Debug\net40\Renci.SshNet.Tests.dll /TestCaseFilter:"TestCategory!=integration&TestCategory!=LongRunning"
vstest.console /logger:Appveyor src\Renci.SshNet.Tests\bin\Debug\net35\Renci.SshNet.Tests.dll /TestCaseFilter:"TestCategory!=integration&TestCategory!=LongRunning"
vstest.console /logger:Appveyor src\Renci.SshNet.Tests\bin\Debug\net462\Renci.SshNet.Tests.dll /TestCaseFilter:"TestCategory!=integration" --blame
vstest.console /logger:Appveyor src\Renci.SshNet.Tests\bin\Debug\net7.0\Renci.SshNet.Tests.dll /TestCaseFilter:"TestCategory!=integration" --blame
+22 -97
View File
@@ -8,86 +8,41 @@
<MSBuildTasksPackageId>MSBuildTasks</MSBuildTasksPackageId>
<MSBuildTasksPackageVersion>1.5.0.214</MSBuildTasksPackageVersion>
</PropertyGroup>
<ItemGroup>
<VisualStudioVersionClassic Include="2012">
<SolutionFile>$(MSBuildThisFileDirectory)..\src\Renci.SshNet.VS2012.sln</SolutionFile>
<ToolsVersion>14.0</ToolsVersion>
<VisualStudioVersion>14.0</VisualStudioVersion>
</VisualStudioVersionClassic>
<VisualStudioVersionClassic Include="2015">
<SolutionFile>$(MSBuildThisFileDirectory)..\src\Renci.SshNet.VS2015.sln</SolutionFile>
<ToolsVersion>14.0</ToolsVersion>
<VisualStudioVersion>14.0</VisualStudioVersion>
</VisualStudioVersionClassic>
</ItemGroup>
<ItemGroup>
<VisualStudioVersionModern Include="2019">
<SolutionFile>$(MSBuildThisFileDirectory)..\src\Renci.SshNet.VS2019.sln</SolutionFile>
<VisualStudioVersion>16.0</VisualStudioVersion>
<VisualStudioVersionModern Include="2022">
<SolutionFile>$(MSBuildThisFileDirectory)..\src\Renci.SshNet.sln</SolutionFile>
<VisualStudioVersion>17.0</VisualStudioVersion>
</VisualStudioVersionModern>
</ItemGroup>
<ItemGroup>
<TargetFrameworkClassic Include="Windows Phone Silverlight 7.1">
<OutputDirectory>Renci.SshNet.WindowsPhone\bin\$(Configuration)</OutputDirectory>
<Moniker>wp71</Moniker>
</TargetFrameworkClassic>
<TargetFrameworkClassic Include="Windows Phone Silverlight 8.0">
<OutputDirectory>Renci.SshNet.WindowsPhone8\bin\$(Configuration)</OutputDirectory>
<Moniker>wp8</Moniker>
</TargetFrameworkClassic>
<TargetFrameworkClassic Include="Silverlight 4">
<OutputDirectory>Renci.SshNet.Silverlight\bin\$(Configuration)</OutputDirectory>
<Moniker>sl4</Moniker>
</TargetFrameworkClassic>
<TargetFrameworkClassic Include="Silverlight 5">
<OutputDirectory>Renci.SshNet.Silverlight5\bin\$(Configuration)</OutputDirectory>
<Moniker>sl5</Moniker>
</TargetFrameworkClassic>
<TargetFrameworkClassic Include="Universal Windows Platform 10">
<OutputDirectory>Renci.SshNet.UAP10\bin\$(Configuration)</OutputDirectory>
<Moniker>uap10</Moniker>
</TargetFrameworkClassic>
</ItemGroup>
<ItemGroup>
<TargetFrameworkModern Include=".NET Framework 3.5">
<OutputDirectory>Renci.SshNet\bin\$(Configuration)\net35</OutputDirectory>
<Moniker>net35</Moniker>
</TargetFrameworkModern>
<TargetFrameworkModern Include=".NET Framework 4.0">
<OutputDirectory>Renci.SshNet\bin\$(Configuration)\net40</OutputDirectory>
<Moniker>net40</Moniker>
</TargetFrameworkModern>
<TargetFrameworkModern Include=".NETStandard 1.3">
<OutputDirectory>Renci.SshNet\bin\$(Configuration)\netstandard1.3</OutputDirectory>
<Moniker>netstandard1.3</Moniker>
<TargetFrameworkModern Include=".NET Framework 4.6.2">
<OutputDirectory>Renci.SshNet\bin\$(Configuration)\net462</OutputDirectory>
<Moniker>net462</Moniker>
</TargetFrameworkModern>
<TargetFrameworkModern Include=".NETStandard 2.0">
<OutputDirectory>Renci.SshNet\bin\$(Configuration)\netstandard2.0</OutputDirectory>
<Moniker>netstandard2.0</Moniker>
</TargetFrameworkModern>
<TargetFrameworkModern Include=".NETStandard 2.1">
<OutputDirectory>Renci.SshNet\bin\$(Configuration)\netstandard2.1</OutputDirectory>
<Moniker>netstandard2.1</Moniker>
</TargetFrameworkModern>
<TargetFrameworkModern Include=".NET 6.0">
<OutputDirectory>Renci.SshNet\bin\$(Configuration)\net6.0</OutputDirectory>
<Moniker>net6.0</Moniker>
</TargetFrameworkModern>
<TargetFrameworkModern Include=".NET 7.0">
<OutputDirectory>Renci.SshNet\bin\$(Configuration)\net7.0</OutputDirectory>
<Moniker>net7.0</Moniker>
</TargetFrameworkModern>
</ItemGroup>
<Target Name="CleanClassic" DependsOnTargets="CleanSolutionClassic">
<RemoveDir Directories="$(MSBuildThisFileDirectory)target"/>
</Target>
<Target Name="CleanModern" DependsOnTargets="CleanSolutionModern">
<RemoveDir Directories="$(MSBuildThisFileDirectory)target"/>
</Target>
<Target Name="CleanSolutionClassic" Outputs="%(VisualStudioVersionClassic.Identity)">
<ItemGroup>
<ProjectToBuild Remove="@(ProjectToBuild)"/>
<ProjectToBuild Include="%(VisualStudioVersionClassic.SolutionFile)">
<Properties>Configuration=Release;VisualStudioVersion=%(VisualStudioVersionClassic.VisualStudioVersion)</Properties>
</ProjectToBuild>
</ItemGroup>
<MSBuild Projects="@(ProjectToBuild)" ToolsVersion="%(VisualStudioVersionClassic.ToolsVersion)" Targets="Clean"/>
</Target>
<Target Name="CleanSolutionModern" Outputs="%(VisualStudioVersionModern.Identity)">
<ItemGroup>
@@ -99,26 +54,11 @@
<MSBuild Projects="@(ProjectToBuild)" Targets="Clean"/>
</Target>
<Target Name="RestoreNuGetPackagesClassic" DependsOnTargets="DownloadNuGet" Outputs="%(VisualStudioVersionClassic.Identity)">
<Message Text="Restoring nuget packages for '%(VisualStudioVersionClassic.SolutionFile)'..." Importance="High"/>
<Exec Command="$(NuGetExe) restore &quot;%(VisualStudioVersionClassic.SolutionFile)&quot;"/>
</Target>
<Target Name="RestoreNuGetPackagesModern" DependsOnTargets="DownloadNuGet" Outputs="%(VisualStudioVersionModern.Identity)">
<Message Text="Restoring nuget packages for '%(VisualStudioVersionModern.SolutionFile)'..." Importance="High"/>
<Exec Command="$(NuGetExe) restore &quot;%(VisualStudioVersionModern.SolutionFile)&quot;"/>
</Target>
<Target Name="BuildClassic" DependsOnTargets="RestoreNuGetPackagesClassic" Outputs="%(VisualStudioVersionClassic.Identity)">
<ItemGroup>
<ProjectToBuild Remove="@(ProjectToBuild)"/>
<ProjectToBuild Include="%(VisualStudioVersionClassic.SolutionFile)">
<Properties>Configuration=Release;VisualStudioVersion=%(VisualStudioVersionClassic.VisualStudioVersion)</Properties>
</ProjectToBuild>
</ItemGroup>
<MSBuild Projects="@(ProjectToBuild)" ToolsVersion="%(VisualStudioVersionClassic.ToolsVersion)" Targets="Rebuild"/>
</Target>
<Target Name="BuildModern" DependsOnTargets="RestoreNuGetPackagesModern" Outputs="%(VisualStudioVersionModern.Identity)">
<ItemGroup>
<ProjectToBuild Remove="@(ProjectToBuild)"/>
@@ -131,12 +71,7 @@
<Target Name="Package" DependsOnTargets="CreateNuGetPackage;CreateBinPackage;GenerateHelpFile"/>
<Target Name="ValidatePackage" DependsOnTargets="ValidatePackageClassic;ValidatePackageModern"/>
<Target Name="ValidatePackageClassic" DependsOnTargets="CheckNuGetPackageDirectory" Outputs="%(TargetFrameworkClassic.Identity)">
<Error Text="The 'Renci.SshNet.dll' file is not available for %(TargetFrameworkClassic.Identity) in $(NuGetPackageDirectory)\lib\%(TargetFrameworkClassic.Moniker)." Condition="!Exists('$(NuGetPackageDirectory)\lib\%(TargetFrameworkClassic.Moniker)\Renci.SshNet.dll')"/>
<Error Text="The 'Renci.SshNet.xml' file is not available for %(TargetFrameworkClassic.Identity) in $(NuGetPackageDirectory)\lib\%(TargetFrameworkClassic.Moniker)." Condition="!Exists('$(NuGetPackageDirectory)\lib\%(TargetFrameworkClassic.Moniker)\Renci.SshNet.xml')"/>
</Target>
<Target Name="ValidatePackage" DependsOnTargets="ValidatePackageModern"/>
<Target Name="ValidatePackageModern" DependsOnTargets="CheckNuGetPackageDirectory" Outputs="%(TargetFrameworkModern.Identity)">
<Error Text="The 'Renci.SshNet.dll' file is not available for %(TargetFrameworkModern.Identity) in $(NuGetPackageDirectory)\lib\%(TargetFrameworkModern.Moniker)." Condition="!Exists('$(NuGetPackageDirectory)\lib\%(TargetFrameworkModern.Moniker)\Renci.SshNet.dll')"/>
@@ -153,16 +88,6 @@
<Move SourceFiles="$(MSBuildThisFileDirectory)target\help\SshNet.Help.chm" DestinationFiles="$(MSBuildThisFileDirectory)target\SSH.NET-$(ReleaseVersion)-help.chm"/>
</Target>
<Target Name="PreparePackageClassic" DependsOnTargets="BuildClassic;CheckNuGetPackageDirectory;CheckBinaryZipPackageDirectory" Outputs="%(TargetFrameworkClassic.Identity)">
<ItemGroup>
<BuildOutput Remove="@(BuildOutput)"/>
<BuildOutput Include="$(MSBuildThisFileDirectory)..\src\%(TargetFrameworkClassic.OutputDirectory)\Renci.SshNet.dll"/>
<BuildOutput Include="$(MSBuildThisFileDirectory)..\src\%(TargetFrameworkClassic.OutputDirectory)\Renci.SshNet.xml"/>
</ItemGroup>
<Copy SourceFiles="@(BuildOutput)" DestinationFolder="$(NuGetPackageDirectory)\lib\%(TargetFrameworkClassic.Moniker)"/>
<Copy SourceFiles="@(BuildOutput)" DestinationFolder="$(BinPackageDirectory)\lib\%(TargetFrameworkClassic.Moniker)"/>
</Target>
<Target Name="PreparePackageModern" DependsOnTargets="BuildModern;CheckNuGetPackageDirectory" Outputs="%(TargetFrameworkModern.Identity)">
<ItemGroup>
<BuildOutput Remove="@(BuildOutput)"/>
+6 -25
View File
@@ -6,7 +6,7 @@
<title>SSH.NET</title>
<authors>Renci</authors>
<owners>olegkap,drieseng</owners>
<licenseUrl>https://github.com/sshnet/SSH.NET/blob/master/LICENSE</licenseUrl>
<license type="expression">MIT</license>
<projectUrl>https://github.com/sshnet/SSH.NET/</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>SSH.NET is a Secure Shell (SSH) library for .NET, optimized for parallelism and with broad framework support.</description>
@@ -16,38 +16,19 @@
<language>en-US</language>
<tags>ssh scp sftp</tags>
<dependencies>
<group targetFramework="net35" />
<group targetFramework="net40" />
<group targetFramework="netstandard1.3">
<dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
<dependency id="System.Diagnostics.TraceSource" version="4.3.0" />
<dependency id="System.Net.NameResolution" version="4.3.0" />
<dependency id="System.Net.Sockets" version="4.3.0" />
<dependency id="System.Threading.Thread" version="4.3.0" />
<dependency id="System.Threading.ThreadPool" version="4.3.0" />
<dependency id="System.Threading.Timer" version="4.3.0" />
<dependency id="System.Xml.XmlDocument" version="4.3.0" />
<dependency id="System.Xml.XPath.XmlDocument" version="4.3.0" />
</group>
<group targetFramework="net462" />
<group targetFramework="netstandard2.0">
<dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
</group>
<group targetFramework="sl4">
</group>
<group targetFramework="netstandard2.1">
<dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
</group>
<group targetFramework="sl5">
<group targetFramework="net6.0">
<dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
</group>
<group targetFramework="wp71">
<group targetFramework="net7.0">
<dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
</group>
<group targetFramework="wp8">
<dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
</group>
<group targetFramework="uap10.0">
<dependency id="SshNet.Security.Cryptography" version="[1.3.0]" />
<dependency id="System.Xml.XPath.XmlDocument" version="4.3.0" />
</group>
</dependencies>
</metadata>
</package>
+14 -23
View File
@@ -1,21 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<!--
The configuration and platform will be used to determine which assemblies to include from solution and
project documentation sources
-->
<!-- A target framework version is required by Visual Studio. It can be any version with a targeting pack installed. -->
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<!-- The configuration and platform will be used to determine which assemblies to include from solution and
project documentation sources -->
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{f7266fb1-f50a-4a5b-b35a-5ea8ebdc1be9}</ProjectGuid>
<SHFBSchemaVersion>2015.6.5.0</SHFBSchemaVersion>
<SHFBSchemaVersion>2017.9.26.0</SHFBSchemaVersion>
<!-- AssemblyName, Name, and RootNamespace are not used by SHFB but Visual Studio adds them anyway -->
<AssemblyName>Documentation</AssemblyName>
<RootNamespace>Documentation</RootNamespace>
<Name>Documentation</Name>
<!-- SHFB properties -->
<FrameworkVersion>.NET Framework 4.0</FrameworkVersion>
<FrameworkVersion>.NET Framework 4.6.2</FrameworkVersion>
<OutputPath>..\target\help</OutputPath>
<HtmlHelpName>SshNet.Help</HtmlHelpName>
<Language>en-US</Language>
@@ -24,25 +25,15 @@
<SyntaxFilters>C#</SyntaxFilters>
<SdkLinkTarget>Blank</SdkLinkTarget>
<RootNamespaceContainer>False</RootNamespaceContainer>
<PresentationStyle>VS2010</PresentationStyle>
<PresentationStyle>VS2013</PresentationStyle>
<Preliminary>False</Preliminary>
<NamingMethod>Guid</NamingMethod>
<HelpTitle>SSH.NET Client Library Documenation</HelpTitle>
<HelpTitle>SSH.NET Client Library Documentation</HelpTitle>
<ContentPlacement>AboveNamespaces</ContentPlacement>
<ComponentConfigurations>
<ComponentConfig id="Code Block Component" enabled="True">
<component id="Code Block Component">
<basePath value="{@HtmlEncProjectFolder}" />
<outputPaths>{@HelpFormatOutputPaths}</outputPaths>
<allowMissingSource value="false" />
<removeRegionMarkers value="false" />
<colorizer syntaxFile="{@SHFBROOT}\PresentationStyles\Colorizer\highlight.xml" styleFile="{@SHFBROOT}\PresentationStyles\Colorizer\highlight.xsl" stylesheet="{@SHFBROOT}\PresentationStyles\Colorizer\highlight.css" scriptFile="{@SHFBROOT}\PresentationStyles\Colorizer\highlight.js" disabled="{@DisableCodeBlockComponent}" language="cs" tabSize="0" numberLines="false" outlining="false" keepSeeTags="false" defaultTitle="true" />
</component>
</ComponentConfig>
</ComponentConfigurations>
<DocumentationSources>
<DocumentationSource sourceFile="..\..\src\Renci.SshNet\bin\Release\net40\Renci.SshNet.dll" xmlns="" />
<DocumentationSource sourceFile="..\..\src\Renci.SshNet\bin\Release\net40\Renci.SshNet.xml" xmlns="" />
<DocumentationSource sourceFile="..\..\src\Renci.SshNet\bin\Release\net462\Renci.SshNet.dll" xmlns="" />
<DocumentationSource sourceFile="..\..\src\Renci.SshNet\bin\Release\net462\Renci.SshNet.xml" xmlns="" />
</DocumentationSources>
<MissingTags>Summary, Parameter, Returns, AutoDocumentCtors, TypeParameter, AutoDocumentDispose</MissingTags>
<BuildAssemblerVerbosity>OnlyWarningsAndErrors</BuildAssemblerVerbosity>
@@ -54,7 +45,7 @@
<CleanIntermediates>True</CleanIntermediates>
</PropertyGroup>
<!-- There are no properties for these groups. AnyCPU needs to appear in order for Visual Studio to perform
the build. The others are optional common platform types that may appear. -->
the build. The others are optional common platform types that may appear. -->
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
@@ -73,4 +64,4 @@
</PropertyGroup>
<!-- Import the SHFB build targets -->
<Import Project="$(SHFBROOT)\SandcastleHelpFileBuilder.targets" />
</Project>
</Project>
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

+9
View File
@@ -0,0 +1,9 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCPSPglZ3
w/7DmCJxYohONLAAAAEAAAAAEAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlz
dHAyNTYAAABBBK6YY2NwwLDSMnJTD+a4OfitCDuG/MnY/AstPgh54xMrZF6Qr0U1H6kRMK
Y6JJsj31CI97qDYrnTA00Sx5Jy6ywAAACwq4qisorVCP6yvrmf/fcPacX4+FVEmrHNn3fW
TiYsat7oKoItqTiDaHkIloSX93ue3fzcKXpGPR/qnpu4SezkhL9Uk6ntiwO4coB/kbEnjk
IFY6ZK0HENRXkdIuDG9qmoB0wjVPJ6L9e5RWZwiCPvNI2O60bpKOUs+tUSah1W7eTWy5Ss
ttdTgmwqS84c5+uitK1DJh2jsDqfdGm7h1XpDJsRmIEXxTVu/EdtD0hZ/x4=
-----END OPENSSH PRIVATE KEY-----
+9
View File
@@ -0,0 +1,9 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSP3ZTb37LFvSmKweu03A5s/cwcw3+3
jL1LqQK6D929xY1J2J6S91LXOBpBfz4l+8Ng7sWhu9P/hF/wmB2QRygrAAAAqMq583bKuf
N2AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI/dlNvfssW9KYrB
67TcDmz9zBzDf7eMvUupAroP3b3FjUnYnpL3Utc4GkF/PiX7w2DuxaG70/+EX/CYHZBHKC
sAAAAhALVqID3K/N7IazKNbhrg09r7rLLtjy81RLV+VDxloQnxAAAAC3NyaW5rZXNATkVP
AQIDBA==
-----END OPENSSH PRIVATE KEY-----
@@ -0,0 +1,11 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAMZphcrt
UKJMlSabtzt2GdAAAAEAAAAAEAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlz
dHAzODQAAABhBFL5NEL9uRhgkF2q+8m58EvtZq4mDGgcVEzafPRuNIn1018m9KuqNpOQ6d
+435n+MRYThe4MUdijSIDuopX2i14Z35oKZ9x2LsV+RxQczjmbnoWZdvgcvdOo6jiJdY7X
JwAAAOBvXOaTq8vPRy7y5BBzr26QAYouJfGprYOqpywiIAZaICu0FJ8EXmmen6310CTG6Z
CZ4VhC5MWCWRYTaOnPNn8FvGqo2bxEqWZmyZfVvv1Z35MtSAZEfwgfXaOZKJ/lPKsRndg5
okpqNU1aG2u+4J7eZ7QyCD/1RCCEL5wwVcrDeuMkTDPpnJc1NEGz8HbfcZ5xZavrz6Wa9t
tX7pFICqK9IIeOGMJ2WRXR6sQGyag+jNn9KmsIya7hkNJVeZeY2GKAk2s/0vxfYx9RFD55
ewB34oHyTdxAQT3L+FZT6XfRHw==
-----END OPENSSH PRIVATE KEY-----
+10
View File
@@ -0,0 +1,10 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNlY2RzYS
1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQRTP1DMXoHgW+RX+S/NxUEElou1cmLD
6CEiR+zpzaGG6mzl6LhUY+Z3f2M3u4u7tcM8TgB/jiHbnI9TaeN5QK4HX1D9DXkH5RhfnL
frm3kCTNoCFKd0Wa/QAvKrlNKiRi8AAADYlABjHJQAYxwAAAATZWNkc2Etc2hhMi1uaXN0
cDM4NAAAAAhuaXN0cDM4NAAAAGEEUz9QzF6B4FvkV/kvzcVBBJaLtXJiw+ghIkfs6c2hhu
ps5ei4VGPmd39jN7uLu7XDPE4Af44h25yPU2njeUCuB19Q/Q15B+UYX5y365t5AkzaAhSn
dFmv0ALyq5TSokYvAAAAMAXLUgK32yWzUrpeLzpdFB2/eiNnkxQlu5OneTPukKcZYclfgo
jv0YHK5LCvAtF8lwAAAAtzcmlua2VzQE5FTwECAwQF
-----END OPENSSH PRIVATE KEY-----
@@ -0,0 +1,12 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBWjIyzbM
MQ3UPE8BiQ0n4LAAAAEAAAAAEAAACsAAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlz
dHA1MjEAAACFBAH9BVM6bRhbELtgdMGsin5lM42R2EWoT+6Akakl5rQy2tHHLIYGLEfaqI
+0iUo2V6MxEf9w0hVz6SEsF+yDgyrYPQCIieaB1oBvIl+PZmL1XsuAXs2uMRsNJb4myGU/
DiekxqzIPa0LMrBZ4xmErcn5Gazkw1EA0B3HoaW5wj+geI/efQAAARDi+GGTYH1T+5Dd8N
EVCiL+J7fm8uP8yAcvQNh3JBYIf1g/GZ0hJDuA47fcTzXEfTGZLGWdgaE8cxIUICpjBoak
EpNS1HyhqYZAt2J8o/14t2GbXczJfoQLOIQl2S1zXQ9shof12odu9DGcBhSAz9hswlndBE
d99uCz/ymzwQ0i2Pp+urUXo7+YXB6HMh9YTMeGQAiDJFO3NPDqDczfUECtTUkQMhy8r06m
hAp/oZ6K1KBbZzdc0xyqDePKAqqyHnN4FD7Wfv11SWoOhlUcEVg2ZvNj/O+CsoWzMpN+dt
DPKZHmH/kegWKBsdtAC9f5Hg3b2oQAK0pKghms1+/J9iilnIMwv80CPzGdv0YAG9Vx5w==
-----END OPENSSH PRIVATE KEY-----
+12
View File
@@ -0,0 +1,12 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS
1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQAa7p4WVga+08qu160BrdzKyRNJMQC
eGhFluNdq73/uaW3qlqoEiaNtc5uB7HHyjCWQTmfzrSRLRZ7YBwUancwyh4Aq6gBgGsXVz
wNvY3kxRDlGrSfMmsHlXz41dgw9wm1fBcf97niexRQ/xGlhkyf7IYlQ/s5BpXF2lS9l0H5
hBippRgAAAEI/9prf//aa38AAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ
AAAIUEAGu6eFlYGvtPKrtetAa3cyskTSTEAnhoRZbjXau9/7mlt6paqBImjbXObgexx8ow
lkE5n860kS0We2AcFGp3MMoeAKuoAYBrF1c8Db2N5MUQ5Rq0nzJrB5V8+NXYMPcJtXwXH/
e54nsUUP8RpYZMn+yGJUP7OQaVxdpUvZdB+YQYqaUYAAAAQXwQnI20tNxwLKHPMDmumblD
b0sBqW5Y9248L//x4sWFrkjk6k1LcZno9KLqz8+tIFMJ5sji+axRoUZCXb3cIPzPAAAAC3
NyaW5rZXNATkVP
-----END OPENSSH PRIVATE KEY-----
+28
View File
@@ -0,0 +1,28 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCoWhCSaG
psKT80oPIOAlqJAAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQCxOfnmxC1g
mmX18LCBG/X73BoCXQEBEAJz3V9w0FMTgUBebK3fkfOMLNzWn5aMR608wQHOEPYhHffCJf
dJR6lUWLHZz5+EZRfM9oHpysMtToYQoGtb9xM7D5J3lnWZgSea2R7xSeqpClN5nQUMGu8y
2d2S3g9o1vrTdeu71u09QFOx2AXBPUmCjpCuBlNyYEEQMfRMtQ6PDbPdvLM1uylbQqB+/6
jMsCyEFvlLit9GcZ7ItKQN+jsZNmP+f7ytVXLsZTjPLd5mWWrf6T1T1Xt9DBoLXnMrmDiC
f/EXiTYonIO6B0FHvNUCrDzZ7rxIebqePLes1Q2yoUqmC8g+cww3AAADwNRNGSnB7cRpgU
BxdyC0ofj0hUONXjmoT+OGPph9lgZMUnzcon9Z1bpsJuMoRXL14Cdbds7YPmw1YB94Uc+S
8QexLJG0wGel2yvzJhU+QFsLeVRS4tayERFXGCoVpu7RunEYy+hvaiX5CD+luEkiarfj9I
N8+9QUMhDYkELwWBV4rde18Vr8m1P1FuFgqikY0TfSKUGCkvjl4FvDxrxqsewaEkkzwRTI
PhOFCCM5jBPWE+uWVcwKoidvAqcNbmwIzDNZGwXtrAvYYzZa62C/MNLHuFU1weuJiM8sYa
6iKrk681BrrpGcSEZEXd41CFY3BWlIDTozrWn03xFlIpeLG2YMPcuYqFhR/41BJfa+fW5B
Ei0SuUx2xjdRiamqpPku4H6ulkjl0KlFCr976Y2V1JZMQh7bd0huubmf4P4poBk6ZgGpSf
snhcv1HjCVkvfA2yhOcXogzK2HOZgDS5sdSb/kUGURdjlj6ccSzc3OYaHAy9gZXj8Q58pA
4KrXTDlCJ9BTR8PIND54j6gMKu5ijX0TP9nJf/hG9GXx+Xss8T3xdPxdNBapPCcuxGZGJN
H+KFqrpmZYHm0evqFPS7BCUp2VvID6SMgrTYiH0IIbMHLStfdNchtn3EudMbW9vRhxg3Do
npT7Px2JYp87PNoHg2eOx0yGy9r81n2+Wi7SpGCWD8MFfxqd4JIQ8+zjrIRAA1q53uuSUh
m/hlmJWEjQWmcBw5bKrOU0CfGGoT3o6HWYRQ9d5+kKeoS+dOINxxf80G5b7vOrE3PbFxT3
W8zwRd90Msr3LXgPaN0V4RJeBX38e0EvVbArL2MgSs/BC5aID0N0Sqiu+13AqqNYxj6RH2
FA7FN+BBa16fvdi5h5kNnZUrQUKOAImjEE494O8uGKQImviGqB5PY6DJqHPTtn7RSwFx9E
rR7nbAZPTucIN/OIfURxTedhROk0PXjWnwpjuz+UpaMRWqgWTv3bLOuqorqMLibAFLRQEQ
6pR0wbmTpTfEW1jNmAohxB4N14YdSfhThzkCAgpQW6UCLc83y3EDzQFi5862a+2ixULKhK
220tZRk2GU7OFAPRpgQ/sxptGqZbNdOV80wk1MgykoFkoptRkm7bfJcdLHZnP7E6yU0ssP
rCbQlfD0/dD2QE/7HqxHsipNNuEagULjK6WUYXkpx1Siq2vecjZw8dNp7EBh+KlujEm+Dr
R7KFdFCw8DUwrzXwfMIogeRVbW8H0/fQEqsX5oPLTEOnNBjzf8pHush7CCrprbo0ZK3xFp
Vr3LUCoA==
-----END OPENSSH PRIVATE KEY-----
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAQEA7W7Oigi7Hj1msa2l8HimLPzagVmE/CseMKCRxPMTtWTvoZdMt7hf
kthWA7h20a7oSSeH2t7FqeOpVNGFYvRV3BVWsKZJMRcdJiTCZAjH38rYk90XvZ3mKrKN/3
fcQsh4OiPnWqT6HSqg14oiV8UPtUwnE65skWSxvxt+ELlVpCqG5vE2O/GNDKQE7FzYt6vQ
ihMFSABBqjvau0mjX002KYiCMr6vgl8XYkDA4n5+JyQSQxznnfrL93ZoaqujRP2bT5UhXg
bzr8HFwuqQGvURECZTTI8Biv/6tOIn9x2hEaN6vxDLtXc99E2d6NW6cdriwaGFJ4jgVWDE
5mU006XdPQAAA8jzN6zf8zes3wAAAAdzc2gtcnNhAAABAQDtbs6KCLsePWaxraXweKYs/N
qBWYT8Kx4woJHE8xO1ZO+hl0y3uF+S2FYDuHbRruhJJ4fa3sWp46lU0YVi9FXcFVawpkkx
Fx0mJMJkCMffytiT3Re9neYqso3/d9xCyHg6I+dapPodKqDXiiJXxQ+1TCcTrmyRZLG/G3
4QuVWkKobm8TY78Y0MpATsXNi3q9CKEwVIAEGqO9q7SaNfTTYpiIIyvq+CXxdiQMDifn4n
JBJDHOed+sv3dmhqq6NE/ZtPlSFeBvOvwcXC6pAa9REQJlNMjwGK//q04if3HaERo3q/EM
u1dz30TZ3o1bpx2uLBoYUniOBVYMTmZTTTpd09AAAAAwEAAQAAAQEA6Xgq+gppzOt9nrts
z5Ajf1tHlSesn7XaYuCRVgPb3mOZSuEW3BUdTa0Sr2fk1nzSBpUrfqnN3idyK2g3bD1sbB
RDgUKR+AaNcCN3TpxfxgyVeJhQLvEkEdovzQRUfwrXRfxmE3jkRGfVbvxylrG8p35xcmXy
del46r2i8dj8gIY3tKp0RMvZ4ZlNbhWHPd5OxyHWL9e3gbOSyIyjQgTKZezhzErS/X/KJ/
XYqzyBAqNqZ2Rg1kKiHRlHS6KEI2tyFwYfh+Rb6L9xch1SqOtQhTWirmxS25dpGD2jgalX
eyiw8PmuqTiWCqUmUMx6MdF3tFsirr54K4QA9kqMeaRLtQAAAIEAsUQT0Uhq7l0ugTd4Y9
89bH6eW0fol21/m7B5zkJQepNadUPTs188uvv4inW8n2O3RCanXWHZfCJ1AsR/MEEW2C6Q
DtvqKXHbzfWQlCYSVxB17CjURKa8fNaIAk98zgmNNwO53NBleyrUhPgvm3xt7ACgpzXY5R
wNJL8/a0WOmgwAAACBAP508Op6wWPAwn1JfBZuqQtjcfnJeN4NkYQBdybn0vVu5UdyqSQL
a8hlAzROhA+qJvrlsZgM9h8CyLTyuim8likZHocwO13zBTdVaQ8c2lJvf2uXTIXNZHseS7
ITkfBiO1hSB4z8RDkOr35mGfdbyJIFAwFZF4Xs8WnQF+vHEadrAAAAgQDu329eCwVFf9g0
zNHfZu31p0WtErcsRv57fq+UoPtov8nxUF71oOWe5KSGnGtMICI31kBtPhUbvfOmuqNrgJ
BjgjbPQmi0xSAE5L3QuEKRNjlaE3/WadKBwzhJDtauuYk1ifkrdAVp67XyQ5puyuGgVaQB
NPbrxA9g1IbyeL4/9wAAAAtzcmlua2VzQE5FTwECAwQFBg==
-----END OPENSSH PRIVATE KEY-----
@@ -0,0 +1,66 @@
using BenchmarkDotNet.Attributes;
using Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers;
using Renci.SshNet.Common;
using Renci.SshNet.Security;
namespace Renci.SshNet.Benchmarks.Common
{
[MemoryDiagnoser]
[ShortRunJob]
public class HostKeyEventArgsBenchmarks
{
private readonly KeyHostAlgorithm _keyHostAlgorithm;
public HostKeyEventArgsBenchmarks()
{
_keyHostAlgorithm = GetKeyHostAlgorithm();
}
private static KeyHostAlgorithm GetKeyHostAlgorithm()
{
using (var s = typeof(RsaCipherBenchmarks).Assembly.GetManifestResourceStream("Renci.SshNet.Benchmarks.Data.Key.RSA.txt"))
{
var privateKey = new PrivateKeyFile(s);
return (KeyHostAlgorithm) privateKey.HostKeyAlgorithms.First();
}
}
[Benchmark()]
public HostKeyEventArgs Constructor()
{
return new HostKeyEventArgs(_keyHostAlgorithm);
}
[Benchmark()]
public (string, string) CalculateFingerPrintSHA256AndMD5()
{
var test = new HostKeyEventArgs(_keyHostAlgorithm);
return (test.FingerPrintSHA256, test.FingerPrintMD5);
}
[Benchmark()]
public string CalculateFingerPrintSHA256()
{
var test = new HostKeyEventArgs(_keyHostAlgorithm);
return test.FingerPrintSHA256;
}
[Benchmark()]
public byte[] CalculateFingerPrint()
{
var test = new HostKeyEventArgs(_keyHostAlgorithm);
return test.FingerPrint;
}
[Benchmark()]
public string CalculateFingerPrintMD5()
{
var test = new HostKeyEventArgs(_keyHostAlgorithm);
return test.FingerPrintSHA256;
}
}
}
+27
View File
@@ -0,0 +1,27 @@
using BenchmarkDotNet.Running;
namespace Renci.SshNet.Benchmarks
{
class Program
{
static void Main(string[] args)
{
// Usage examples:
// 1. Run all benchmarks:
// dotnet run -c Release -- --filter *
// 2. List all benchmarks:
// dotnet run -c Release -- --list flat
// 3. Run a subset of benchmarks based on a filter (of a benchmark method's fully-qualified name,
// e.g. "Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers.AesCipherBenchmarks.Encrypt_CBC"):
// dotnet run -c Release -- --filter *Ciphers*
// 4. Run benchmarks and include memory usage statistics in the output:
// dotnet run -c Release -- filter *Rsa* --memory
// 3. Print help:
// dotnet run -c Release -- --help
// See also https://benchmarkdotnet.org/articles/guides/console-args.html
_ = BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
}
}
}
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!--
Even though we're not interested in producing XML docs for test projects, we have to enable this in order to have the .NET Compiler
Platform analyzers produce the IDE0005 (Remove unnecessary import) diagnostic.
To avoid warnings for missing XML docs, we add CS1591 (Missing XML comment for publicly visible type or member) to the NoWarn property.
We can stop producing XML docs for test projects (and remove the NoWarn for CS1591) once the following issue is fixed:
https://github.com/dotnet/roslyn/issues/41640.
-->
<NoWarn>$(NoWarn);CS1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.8" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Renci.SshNet\Renci.SshNet.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\Data\*.txt" LinkBase="Data" />
</ItemGroup>
</Project>
@@ -0,0 +1,38 @@
using BenchmarkDotNet.Attributes;
using Renci.SshNet.Security.Cryptography.Ciphers;
using Renci.SshNet.Security.Cryptography.Ciphers.Modes;
namespace Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers
{
[MemoryDiagnoser]
public class AesCipherBenchmarks
{
private readonly byte[] _key;
private readonly byte[] _iv;
private readonly byte[] _data;
public AesCipherBenchmarks()
{
_key = new byte[32];
_iv = new byte[16];
_data = new byte[256];
Random random = new(Seed: 12345);
random.NextBytes(_key);
random.NextBytes(_iv);
random.NextBytes(_data);
}
[Benchmark]
public byte[] Encrypt_CBC()
{
return new AesCipher(_key, new CbcCipherMode(_iv), null).Encrypt(_data);
}
[Benchmark]
public byte[] Decrypt_CBC()
{
return new AesCipher(_key, new CbcCipherMode(_iv), null).Decrypt(_data);
}
}
}
@@ -0,0 +1,49 @@
using BenchmarkDotNet.Attributes;
using Renci.SshNet.Security;
using Renci.SshNet.Security.Cryptography.Ciphers;
namespace Renci.SshNet.Benchmarks.Security.Cryptography.Ciphers
{
[MemoryDiagnoser]
public class RsaCipherBenchmarks
{
private readonly RsaKey _privateKey;
private readonly RsaKey _publicKey;
private readonly byte[] _data;
public RsaCipherBenchmarks()
{
_data = new byte[128];
Random random = new(Seed: 12345);
random.NextBytes(_data);
using (var s = typeof(RsaCipherBenchmarks).Assembly.GetManifestResourceStream("Renci.SshNet.Benchmarks.Data.Key.RSA.txt"))
{
_privateKey = (RsaKey)new PrivateKeyFile(s).Key;
// The implementations of RsaCipher.Encrypt/Decrypt differ based on whether the supplied RsaKey has private key information
// or only public. So we extract out the public key information to a separate variable.
_publicKey = new RsaKey()
{
Public = _privateKey.Public
};
}
}
[Benchmark]
public byte[] Encrypt()
{
return new RsaCipher(_publicKey).Encrypt(_data);
}
// RSA Decrypt does not work
// [Benchmark]
// public byte[] Decrypt()
// {
// return new RsaCipher(_privateKey).Decrypt(_data);
// }
}
}
@@ -0,0 +1,41 @@
using BenchmarkDotNet.Attributes;
using Renci.SshNet.Security;
using Renci.SshNet.Security.Cryptography;
namespace Renci.SshNet.Benchmarks.Security.Cryptography
{
[MemoryDiagnoser]
public class ED25519DigitalSignatureBenchmarks
{
private readonly ED25519Key _key;
private readonly byte[] _data;
private readonly byte[] _signature;
public ED25519DigitalSignatureBenchmarks()
{
_data = new byte[128];
Random random = new(Seed: 12345);
random.NextBytes(_data);
using (var s = typeof(ED25519DigitalSignatureBenchmarks).Assembly.GetManifestResourceStream("Renci.SshNet.Benchmarks.Data.Key.OPENSSH.ED25519.txt"))
{
_key = (ED25519Key) new PrivateKeyFile(s).Key;
}
_signature = new ED25519DigitalSignature(_key).Sign(_data);
}
[Benchmark]
public byte[] Sign()
{
return new ED25519DigitalSignature(_key).Sign(_data);
}
[Benchmark]
public bool Verify()
{
return new ED25519DigitalSignature(_key).Verify(_data, _signature);
}
}
}
@@ -0,0 +1,32 @@
[*.cs]
#### SYSLIB diagnostics ####
# SYSLIB1045: Use 'GeneratedRegexAttribute' to generate the regular expression implementation at compile-time
#
# TODO: Remove this when https://github.com/sshnet/SSH.NET/issues/1131 is implemented.
dotnet_diagnostic.SYSLIB1045.severity = none
### StyleCop Analyzers rules ###
#### .NET Compiler Platform analysers rules ####
# IDE0007: Use var instead of explicit type
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0007
dotnet_diagnostic.IDE0007.severity = suggestion
# IDE0028: Use collection initializers
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0028
dotnet_diagnostic.IDE0028.severity = suggestion
# IDE0058: Remove unnecessary expression value
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0058
dotnet_diagnostic.IDE0058.severity = suggestion
# IDE0059: Remove unnecessary value assignment
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0059
dotnet_diagnostic.IDE0059.severity = suggestion
# IDE0230: Use UTF-8 string literal
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0230
dotnet_diagnostic.IDE0230.severity = suggestion
@@ -0,0 +1 @@
TestResults/
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.diagnostics>
<trace autoflush="true"/>
<sources>
<source name="SshNet.Logging" switchName="SshNetSwitch" switchType="System.Diagnostics.SourceSwitch">
<listeners>
<!--<add name="SshDotNetTraceFile" />-->
<!--<add name="Console"/>-->
</listeners>
</source>
</sources>
<switches>
<add name="SshNetSwitch" value="Verbose"/>
</switches>
<sharedListeners>
<add name="SshDotNetTraceFile" type="System.Diagnostics.TextWriterTraceListener" initializeData="SshNetTrace.log">
<!--<filter type="System.Diagnostics.EventTypeFilter" initializeData="Warning" />-->
</add>
<add name="Console" type="System.Diagnostics.ConsoleTraceListener" traceOutputOptions="DateTime,Timestamp,ThreadId"/>
</sharedListeners>
</system.diagnostics>
</configuration>
@@ -0,0 +1,84 @@
namespace Renci.SshNet.IntegrationTests
{
public class AuthenticationMethodFactory
{
public PasswordAuthenticationMethod CreatePowerUserPasswordAuthenticationMethod()
{
var user = Users.Admin;
return new PasswordAuthenticationMethod(user.UserName, user.Password);
}
public PrivateKeyAuthenticationMethod CreateRegularUserPrivateKeyAuthenticationMethod()
{
var privateKeyFile = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_rsa");
return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKeyFile);
}
public PrivateKeyAuthenticationMethod CreateRegularUserMultiplePrivateKeyAuthenticationMethod()
{
var privateKeyFile1 = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_rsa");
var privateKeyFile2 = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_rsa");
return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKeyFile1, privateKeyFile2);
}
public PrivateKeyAuthenticationMethod CreateRegularUserPrivateKeyWithPassPhraseAuthenticationMethod()
{
var privateKeyFile = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_rsa_with_pass", "tester");
return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKeyFile);
}
public PrivateKeyAuthenticationMethod CreateRegularUserPrivateKeyWithEmptyPassPhraseAuthenticationMethod()
{
var privateKeyFile = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_rsa_with_pass", null);
return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKeyFile);
}
public PrivateKeyAuthenticationMethod CreateRegularUserPrivateKeyAuthenticationMethodWithBadKey()
{
var privateKeyFile = GetPrivateKey("Renci.SshNet.IntegrationTests.resources.client.id_noaccess.rsa");
return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKeyFile);
}
public PasswordAuthenticationMethod CreateRegulatUserPasswordAuthenticationMethod()
{
return new PasswordAuthenticationMethod(Users.Regular.UserName, Users.Regular.Password);
}
public PasswordAuthenticationMethod CreateRegularUserPasswordAuthenticationMethodWithBadPassword()
{
return new PasswordAuthenticationMethod(Users.Regular.UserName, "xxx");
}
public KeyboardInteractiveAuthenticationMethod CreateRegularUserKeyboardInteractiveAuthenticationMethod()
{
var keyboardInteractive = new KeyboardInteractiveAuthenticationMethod(Users.Regular.UserName);
keyboardInteractive.AuthenticationPrompt += (sender, args) =>
{
foreach (var authenticationPrompt in args.Prompts)
{
authenticationPrompt.Response = Users.Regular.Password;
}
};
return keyboardInteractive;
}
private PrivateKeyFile GetPrivateKey(string resourceName, string passPhrase = null)
{
using (var stream = GetResourceStream(resourceName))
{
return new PrivateKeyFile(stream, passPhrase);
}
}
private Stream GetResourceStream(string resourceName)
{
var type = GetType();
var resourceStream = type.Assembly.GetManifestResourceStream(resourceName);
if (resourceStream == null)
{
throw new ArgumentException($"Resource '{resourceName}' not found in assembly '{type.Assembly.FullName}'.", nameof(resourceName));
}
return resourceStream;
}
}
}
@@ -0,0 +1,427 @@
using Renci.SshNet.Common;
using Renci.SshNet.IntegrationTests.Common;
namespace Renci.SshNet.IntegrationTests
{
[TestClass]
public class AuthenticationTests : IntegrationTestBase
{
private AuthenticationMethodFactory _authenticationMethodFactory;
private IConnectionInfoFactory _connectionInfoFactory;
private IConnectionInfoFactory _adminConnectionInfoFactory;
private RemoteSshdConfig _remoteSshdConfig;
[TestInitialize]
public void SetUp()
{
_authenticationMethodFactory = new AuthenticationMethodFactory();
_connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort, _authenticationMethodFactory);
_adminConnectionInfoFactory = new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort);
_remoteSshdConfig = new RemoteSshd(_adminConnectionInfoFactory).OpenConfig();
}
[TestCleanup]
public void TearDown()
{
_remoteSshdConfig?.Reset();
using (var client = new SshClient(_adminConnectionInfoFactory.Create()))
{
client.Connect();
// Reset the password back to the "regular" password.
using (var cmd = client.RunCommand($"echo \"{Users.Regular.Password}\n{Users.Regular.Password}\" | sudo passwd " + Users.Regular.UserName))
{
Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
}
// Remove password expiration
using (var cmd = client.RunCommand($"sudo chage --expiredate -1 " + Users.Regular.UserName))
{
Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
}
}
}
[TestMethod]
public void Multifactor_PublicKey()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey")
.Update()
.Restart();
var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod());
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
}
}
[TestMethod]
[TestCategory("Authentication")]
public void Multifactor_PublicKey_Connect_Then_Reconnect()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey")
.Update()
.Restart();
var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod());
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
client.Disconnect();
client.Connect();
client.Disconnect();
}
}
[TestMethod]
public void Multifactor_PublicKeyWithPassPhrase()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey")
.Update()
.Restart();
var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyWithPassPhraseAuthenticationMethod());
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
}
}
[TestMethod]
[ExpectedException(typeof(SshPassPhraseNullOrEmptyException))]
public void Multifactor_PublicKeyWithEmptyPassPhrase()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey")
.Update()
.Restart();
var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyWithEmptyPassPhraseAuthenticationMethod());
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
}
}
[TestMethod]
public void Multifactor_PublicKey_MultiplePrivateKey()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey")
.Update()
.Restart();
var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserMultiplePrivateKeyAuthenticationMethod());
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
}
}
[TestMethod]
public void Multifactor_PublicKey_MultipleAuthenticationMethod()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "publickey")
.Update()
.Restart();
var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod(),
_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod());
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
}
}
[TestMethod]
public void Multifactor_KeyboardInteractiveAndPublicKey()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive,publickey")
.WithChallengeResponseAuthentication(true)
.WithKeyboardInteractiveAuthentication(true)
.WithUsePAM(true)
.Update()
.Restart();
var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPasswordAuthenticationMethodWithBadPassword(),
_authenticationMethodFactory.CreateRegularUserKeyboardInteractiveAuthenticationMethod(),
_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod());
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
}
}
[TestMethod]
public void Multifactor_Password_ExceedsPartialSuccessLimit()
{
// configure server to require more successfull authentications from a given method than our partial
// success limit (5) allows
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password,password,password,password,password,password")
.Update()
.Restart();
var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod());
using (var client = new SftpClient(connectionInfo))
{
try
{
client.Connect();
Assert.Fail();
}
catch (SshAuthenticationException ex)
{
Assert.IsNull(ex.InnerException);
Assert.AreEqual("Reached authentication attempt limit for method (password).", ex.Message);
}
}
}
[TestMethod]
public void Multifactor_Password_MatchPartialSuccessLimit()
{
// configure server to require a number of successfull authentications from a given method that exactly
// matches our partial success limit (5)
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password,password,password,password,password")
.Update()
.Restart();
var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod());
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
}
}
[TestMethod]
public void Multifactor_Password_Or_PublicKeyAndKeyboardInteractive()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password publickey,keyboard-interactive")
.WithChallengeResponseAuthentication(true)
.WithKeyboardInteractiveAuthentication(true)
.WithUsePAM(true)
.Update()
.Restart();
var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod(),
_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod());
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
}
}
[TestMethod]
public void Multifactor_Password_Or_PublicKeyAndPassword_BadPassword()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password publickey,password")
.Update()
.Restart();
var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPasswordAuthenticationMethodWithBadPassword(),
_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod());
using (var client = new SftpClient(connectionInfo))
{
try
{
client.Connect();
Assert.Fail();
}
catch (SshAuthenticationException ex)
{
Assert.IsNull(ex.InnerException);
Assert.AreEqual("Permission denied (password).", ex.Message);
}
}
}
[TestMethod]
public void Multifactor_PasswordAndPublicKey_Or_PasswordAndPassword()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password,publickey password,password")
.Update()
.Restart();
var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod(),
_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethodWithBadKey());
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
}
connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserPasswordAuthenticationMethodWithBadPassword(),
_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod());
using (var client = new SftpClient(connectionInfo))
{
try
{
client.Connect();
Assert.Fail();
}
catch (SshAuthenticationException ex)
{
Assert.IsNull(ex.InnerException);
Assert.AreEqual("Permission denied (password).", ex.Message);
}
}
}
[TestMethod]
public void Multifactor_PasswordAndPassword_Or_PublicKey()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password,password publickey")
.Update()
.Restart();
var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod(),
_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethodWithBadKey());
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
}
connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod());
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
}
}
[TestMethod]
public void Multifactor_Password_Or_Password()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password password")
.Update()
.Restart();
var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod());
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
}
connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod(),
_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethodWithBadKey());
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
}
}
[TestMethod]
public void KeyboardInteractive_PasswordExpired()
{
var temporaryPassword = new Random().Next().ToString();
using (var client = new SshClient(_adminConnectionInfoFactory.Create()))
{
client.Connect();
// Temporarity modify password so that when we expire this password, we change reset the password back to
// the "regular" password.
using (var cmd = client.RunCommand($"echo \"{temporaryPassword}\n{temporaryPassword}\" | sudo passwd " + Users.Regular.UserName))
{
Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
}
// Force the password to expire immediately
using (var cmd = client.RunCommand($"sudo chage -d 0 " + Users.Regular.UserName))
{
Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
}
}
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive")
.WithChallengeResponseAuthentication(true)
.WithKeyboardInteractiveAuthentication(true)
.WithUsePAM(true)
.Update()
.Restart();
var keyboardInteractive = new KeyboardInteractiveAuthenticationMethod(Users.Regular.UserName);
int authenticationPromptCount = 0;
keyboardInteractive.AuthenticationPrompt += (sender, args) =>
{
Console.WriteLine(args.Instruction);
foreach (var authenticationPrompt in args.Prompts)
{
Console.WriteLine(authenticationPrompt.Request);
switch (authenticationPromptCount)
{
case 0:
// Regular password prompt
authenticationPrompt.Response = temporaryPassword;
break;
case 1:
// Password expired, provide current password
authenticationPrompt.Response = temporaryPassword;
break;
case 2:
// Password expired, provide new password
authenticationPrompt.Response = Users.Regular.Password;
break;
case 3:
// Password expired, retype new password
authenticationPrompt.Response = Users.Regular.Password;
break;
}
authenticationPromptCount++;
}
};
var connectionInfo = _connectionInfoFactory.Create(keyboardInteractive);
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
Assert.AreEqual(4, authenticationPromptCount);
}
}
[TestMethod]
public void KeyboardInteractiveConnectionInfo()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive")
.WithChallengeResponseAuthentication(true)
.WithKeyboardInteractiveAuthentication(true)
.WithUsePAM(true)
.Update()
.Restart();
var host = SshServerHostName;
var port = SshServerPort;
var username = User.UserName;
var password = User.Password;
#region Example KeyboardInteractiveConnectionInfo AuthenticationPrompt
var connectionInfo = new KeyboardInteractiveConnectionInfo(host, port, username);
connectionInfo.AuthenticationPrompt += delegate (object sender, AuthenticationPromptEventArgs e)
{
Console.WriteLine(e.Instruction);
foreach (var prompt in e.Prompts)
{
Console.WriteLine(prompt.Request);
prompt.Response = password;
}
};
using (var client = new SftpClient(connectionInfo))
{
client.Connect();
// Do something here
client.Disconnect();
}
#endregion
Assert.AreEqual(connectionInfo.Host, SshServerHostName);
Assert.AreEqual(connectionInfo.Username, User.UserName);
}
}
}
@@ -0,0 +1,81 @@
using Renci.SshNet.IntegrationTests.Common;
using Renci.SshNet.TestTools.OpenSSH;
namespace Renci.SshNet.IntegrationTests
{
[TestClass]
public class CipherTests : IntegrationTestBase
{
private IConnectionInfoFactory _connectionInfoFactory;
private RemoteSshdConfig _remoteSshdConfig;
[TestInitialize]
public void SetUp()
{
_connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort);
_remoteSshdConfig = new RemoteSshd(new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort)).OpenConfig();
}
[TestCleanup]
public void TearDown()
{
_remoteSshdConfig?.Reset();
}
[TestMethod]
public void TripledesCbc()
{
DoTest(Cipher.TripledesCbc);
}
[TestMethod]
public void Aes128Cbc()
{
DoTest(Cipher.Aes128Cbc);
}
[TestMethod]
public void Aes192Cbc()
{
DoTest(Cipher.Aes192Cbc);
}
[TestMethod]
public void Aes256Cbc()
{
DoTest(Cipher.Aes256Cbc);
}
[TestMethod]
public void Aes128Ctr()
{
DoTest(Cipher.Aes128Ctr);
}
[TestMethod]
public void Aes192Ctr()
{
DoTest(Cipher.Aes192Ctr);
}
[TestMethod]
public void Aes256Ctr()
{
DoTest(Cipher.Aes256Ctr);
}
private void DoTest(Cipher cipher)
{
_remoteSshdConfig.ClearCiphers()
.AddCipher(cipher)
.Update()
.Restart();
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
client.Disconnect();
}
}
}
}
@@ -0,0 +1,32 @@
namespace Renci.SshNet.IntegrationTests.Common
{
public class ArrayBuilder<T>
{
private readonly List<T> _buffer;
public ArrayBuilder()
{
_buffer = new List<T>();
}
public ArrayBuilder<T> Add(T[] array)
{
return Add(array, 0, array.Length);
}
public ArrayBuilder<T> Add(T[] array, int index, int length)
{
for (var i = 0; i < length; i++)
{
_buffer.Add(array[index + i]);
}
return this;
}
public T[] Build()
{
return _buffer.ToArray();
}
}
}
@@ -0,0 +1,393 @@
using System.Net;
using System.Net.Sockets;
namespace Renci.SshNet.IntegrationTests.Common
{
public class AsyncSocketListener : IDisposable
{
private readonly IPEndPoint _endPoint;
private readonly ManualResetEvent _acceptCallbackDone;
private readonly List<Socket> _connectedClients;
private readonly object _syncLock;
private Socket _listener;
private Thread _receiveThread;
private bool _started;
private string _stackTrace;
public delegate void BytesReceivedHandler(byte[] bytesReceived, Socket socket);
public delegate void ConnectedHandler(Socket socket);
public event BytesReceivedHandler BytesReceived;
public event ConnectedHandler Connected;
public event ConnectedHandler Disconnected;
public AsyncSocketListener(IPEndPoint endPoint)
{
_endPoint = endPoint;
_acceptCallbackDone = new ManualResetEvent(false);
_connectedClients = new List<Socket>();
_syncLock = new object();
ShutdownRemoteCommunicationSocket = true;
}
/// <summary>
/// Gets a value indicating whether the <see cref="Socket.Shutdown(SocketShutdown)"/> is invoked on the <see cref="Socket"/>
/// that is used to handle the communication with the remote host, when the remote host has closed the connection.
/// </summary>
/// <value>
/// <see langword="true"/> to invoke <see cref="Socket.Shutdown(SocketShutdown)"/> on the <see cref="Socket"/> that is used
/// to handle the communication with the remote host, when the remote host has closed the connection; otherwise,
/// <see langword="false"/>. The default is <see langword="true"/>.
/// </value>
public bool ShutdownRemoteCommunicationSocket { get; set; }
public void Start()
{
_listener = new Socket(_endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_listener.Bind(_endPoint);
_listener.Listen(1);
_started = true;
_receiveThread = new Thread(StartListener);
_receiveThread.Start(_listener);
_stackTrace = Environment.StackTrace;
}
public void Stop()
{
_started = false;
lock (_syncLock)
{
foreach (var connectedClient in _connectedClients)
{
try
{
connectedClient.Shutdown(SocketShutdown.Send);
}
catch (Exception ex)
{
Console.Error.WriteLine("[{0}] Failure shutting down socket: {1}",
typeof(AsyncSocketListener).FullName,
ex);
}
DrainSocket(connectedClient);
connectedClient.Dispose();
}
_connectedClients.Clear();
}
_listener?.Dispose();
if (_receiveThread != null)
{
_receiveThread.Join();
_receiveThread = null;
}
}
public void Dispose()
{
Stop();
GC.SuppressFinalize(this);
}
private void StartListener(object state)
{
try
{
var listener = (Socket) state;
while (_started)
{
_ = _acceptCallbackDone.Reset();
_ = listener.BeginAccept(AcceptCallback, listener);
_ = _acceptCallbackDone.WaitOne();
}
}
catch (Exception ex)
{
// On .NET framework when Thread throws an exception then unit tests
// were executed without any problem.
// On new .NET exceptions from Thread breaks unit tests session.
Console.Error.WriteLine("[{0}] Failure in StartListener: {1}",
typeof(AsyncSocketListener).FullName,
ex);
}
}
private void AcceptCallback(IAsyncResult ar)
{
// Signal the main thread to continue
_ = _acceptCallbackDone.Set();
// Get the socket that listens for inbound connections
var listener = (Socket) ar.AsyncState;
// Get the socket that handles the client request
Socket handler;
try
{
handler = listener.EndAccept(ar);
}
catch (SocketException ex)
{
// The listener is stopped through a Dispose() call, which in turn causes
// Socket.EndAccept(...) to throw a SocketException or
// ObjectDisposedException
//
// Since we consider such an exception normal when the listener is being
// stopped, we only write a message to stderr if the listener is considered
// to be up and running
if (_started)
{
Console.Error.WriteLine("[{0}] Failure accepting new connection: {1}",
typeof(AsyncSocketListener).FullName,
ex);
}
return;
}
catch (ObjectDisposedException ex)
{
// The listener is stopped through a Dispose() call, which in turn causes
// Socket.EndAccept(IAsyncResult) to throw a SocketException or
// ObjectDisposedException
//
// Since we consider such an exception normal when the listener is being
// stopped, we only write a message to stderr if the listener is considered
// to be up and running
if (_started)
{
Console.Error.WriteLine("[{0}] Failure accepting new connection: {1}",
typeof(AsyncSocketListener).FullName,
ex);
}
return;
}
// Signal new connection
SignalConnected(handler);
lock (_syncLock)
{
// Register client socket
_connectedClients.Add(handler);
}
var state = new SocketStateObject(handler);
try
{
_ = handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state);
}
catch (SocketException ex)
{
// The listener is stopped through a Dispose() call, which in turn causes
// Socket.BeginReceive(...) to throw a SocketException or
// ObjectDisposedException
//
// Since we consider such an exception normal when the listener is being
// stopped, we only write a message to stderr if the listener is considered
// to be up and running
if (_started)
{
Console.Error.WriteLine("[{0}] Failure receiving new data: {1}",
typeof(AsyncSocketListener).FullName,
ex);
}
}
catch (ObjectDisposedException ex)
{
// The listener is stopped through a Dispose() call, which in turn causes
// Socket.BeginReceive(...) to throw a SocketException or
// ObjectDisposedException
//
// Since we consider such an exception normal when the listener is being
// stopped, we only write a message to stderr if the listener is considered
// to be up and running
if (_started)
{
Console.Error.WriteLine("[{0}] Failure receiving new data: {1}",
typeof(AsyncSocketListener).FullName,
ex);
}
}
}
private void ReadCallback(IAsyncResult ar)
{
// Retrieve the state object and the handler socket
// from the asynchronous state object
var state = (SocketStateObject) ar.AsyncState;
var handler = state.Socket;
int bytesRead;
try
{
// Read data from the client socket.
bytesRead = handler.EndReceive(ar, out var errorCode);
if (errorCode != SocketError.Success)
{
bytesRead = 0;
}
}
catch (SocketException ex)
{
// The listener is stopped through a Dispose() call, which in turn causes
// Socket.EndReceive(...) to throw a SocketException or
// ObjectDisposedException
//
// Since we consider such an exception normal when the listener is being
// stopped, we only write a message to stderr if the listener is considered
// to be up and running
if (_started)
{
Console.Error.WriteLine("[{0}] Failure receiving new data: {1}",
typeof(AsyncSocketListener).FullName,
ex);
}
return;
}
catch (ObjectDisposedException ex)
{
// The listener is stopped through a Dispose() call, which in turn causes
// Socket.EndReceive(...) to throw a SocketException or
// ObjectDisposedException
//
// Since we consider such an exception normal when the listener is being
// stopped, we only write a message to stderr if the listener is considered
// to be up and running
if (_started)
{
Console.Error.WriteLine("[{0}] Failure receiving new data: {1}",
typeof(AsyncSocketListener).FullName,
ex);
}
return;
}
void ConnectionDisconnected()
{
SignalDisconnected(handler);
if (ShutdownRemoteCommunicationSocket)
{
lock (_syncLock)
{
if (!_started)
{
return;
}
try
{
handler.Shutdown(SocketShutdown.Send);
handler.Close();
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.ConnectionReset)
{
// On .NET 7 we got Socker Exception with ConnectionReset from Shutdown method
// when the socket is disposed
}
catch (SocketException ex)
{
throw new Exception("Exception in ReadCallback: " + ex.SocketErrorCode + " " + _stackTrace, ex);
}
catch (Exception ex)
{
throw new Exception("Exception in ReadCallback: " + _stackTrace, ex);
}
_ = _connectedClients.Remove(handler);
}
}
}
if (bytesRead > 0)
{
var bytesReceived = new byte[bytesRead];
Array.Copy(state.Buffer, bytesReceived, bytesRead);
SignalBytesReceived(bytesReceived, handler);
try
{
_ = handler.BeginReceive(state.Buffer, 0, state.Buffer.Length, 0, ReadCallback, state);
}
catch (ObjectDisposedException)
{
// TODO On .NET 7, sometimes we get ObjectDisposedException when _started but only on appveyor, locally it works
ConnectionDisconnected();
}
catch (SocketException ex)
{
if (!_started)
{
throw new Exception("BeginReceive while stopping!", ex);
}
throw new Exception("BeginReceive while started!: " + ex.SocketErrorCode + " " + _stackTrace, ex);
}
}
else
{
ConnectionDisconnected();
}
}
private void SignalBytesReceived(byte[] bytesReceived, Socket client)
{
BytesReceived?.Invoke(bytesReceived, client);
}
private void SignalConnected(Socket client)
{
Connected?.Invoke(client);
}
private void SignalDisconnected(Socket client)
{
Disconnected?.Invoke(client);
}
private static void DrainSocket(Socket socket)
{
var buffer = new byte[128];
try
{
while (true && socket.Connected)
{
var bytesRead = socket.Receive(buffer);
if (bytesRead == 0)
{
break;
}
}
}
catch (SocketException ex)
{
Console.Error.WriteLine("[{0}] Failure draining socket ({1}): {2}",
typeof(AsyncSocketListener).FullName,
ex.SocketErrorCode.ToString("G"),
ex);
}
}
private class SocketStateObject
{
public Socket Socket { get; private set; }
public readonly byte[] Buffer = new byte[1024];
public SocketStateObject(Socket handler)
{
Socket = handler;
}
}
}
}
@@ -0,0 +1,11 @@
namespace Renci.SshNet.IntegrationTests.Common
{
public static class DateTimeAssert
{
public static void AreEqual(DateTime expected, DateTime actual)
{
Assert.AreEqual(expected, actual, $"Expected {expected:o}, but was {actual:o}.");
Assert.AreEqual(expected.Kind, actual.Kind);
}
}
}
@@ -0,0 +1,12 @@
namespace Renci.SshNet.IntegrationTests.Common
{
public static class DateTimeExtensions
{
public static DateTime TruncateToWholeSeconds(this DateTime dateTime)
{
return dateTime.AddMilliseconds(-dateTime.Millisecond)
.AddMicroseconds(-dateTime.Microsecond)
.AddTicks(-(dateTime.Nanosecond / 100));
}
}
}
@@ -0,0 +1,30 @@
using Renci.SshNet.TestTools.OpenSSH;
namespace Renci.SshNet.IntegrationTests.Common
{
internal static class RemoteSshdConfigExtensions
{
private const string DefaultAuthenticationMethods = "password publickey";
public static void Reset(this RemoteSshdConfig remoteSshdConfig)
{
remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, DefaultAuthenticationMethods)
.WithChallengeResponseAuthentication(false)
.WithKeyboardInteractiveAuthentication(false)
.PrintMotd()
.WithLogLevel(LogLevel.Debug3)
.ClearHostKeyFiles()
.AddHostKeyFile(HostKeyFile.Rsa.FilePath)
.ClearSubsystems()
.AddSubsystem(new Subsystem("sftp", "/usr/lib/ssh/sftp-server"))
.ClearCiphers()
.ClearKeyExchangeAlgorithms()
.ClearHostKeyAlgorithms()
.ClearPublicKeyAcceptedAlgorithms()
.ClearMessageAuthenticationCodeAlgorithms()
.WithUsePAM(true)
.Update()
.Restart();
}
}
}
@@ -0,0 +1,255 @@
using System.Net;
using System.Net.Sockets;
using Renci.SshNet.Abstractions;
using Renci.SshNet.Common;
using Renci.SshNet.Messages.Transport;
namespace Renci.SshNet.IntegrationTests.Common
{
class Socks5Handler
{
private readonly IPEndPoint _proxyEndPoint;
private readonly string _userName;
private readonly string _password;
public Socks5Handler(IPEndPoint proxyEndPoint, string userName, string password)
{
_proxyEndPoint = proxyEndPoint;
_userName = userName;
_password = password;
}
public Socket Connect(IPEndPoint endPoint)
{
if (endPoint == null)
{
throw new ArgumentNullException("endPoint");
}
var addressBytes = GetAddressBytes(endPoint);
return Connect(addressBytes, endPoint.Port);
}
public Socket Connect(string host, int port)
{
if (host == null)
{
throw new ArgumentNullException(nameof(host));
}
if (host.Length > byte.MaxValue)
{
throw new ArgumentException($@"Cannot be more than {byte.MaxValue} characters.", nameof(host));
}
var addressBytes = new byte[host.Length + 2];
addressBytes[0] = 0x03;
addressBytes[1] = (byte) host.Length;
Encoding.ASCII.GetBytes(host, 0, host.Length, addressBytes, 2);
return Connect(addressBytes, port);
}
private Socket Connect(byte[] addressBytes, int port)
{
var socket = SocketAbstraction.Connect(_proxyEndPoint, TimeSpan.FromSeconds(5));
// Send socks version number
SocketWriteByte(socket, 0x05);
// Send number of supported authentication methods
SocketWriteByte(socket, 0x02);
// Send supported authentication methods
SocketWriteByte(socket, 0x00); // No authentication
SocketWriteByte(socket, 0x02); // Username/Password
var socksVersion = SocketReadByte(socket);
if (socksVersion != 0x05)
{
throw new ProxyException(string.Format("SOCKS Version '{0}' is not supported.", socksVersion));
}
var authenticationMethod = SocketReadByte(socket);
switch (authenticationMethod)
{
case 0x00:
break;
case 0x02:
// Send version
SocketWriteByte(socket, 0x01);
var username = Encoding.ASCII.GetBytes(_userName);
if (username.Length > byte.MaxValue)
{
throw new ProxyException("Proxy username is too long.");
}
// Send username length
SocketWriteByte(socket, (byte) username.Length);
// Send username
SocketAbstraction.Send(socket, username);
var password = Encoding.ASCII.GetBytes(_password);
if (password.Length > byte.MaxValue)
{
throw new ProxyException("Proxy password is too long.");
}
// Send username length
SocketWriteByte(socket, (byte) password.Length);
// Send username
SocketAbstraction.Send(socket, password);
var serverVersion = SocketReadByte(socket);
if (serverVersion != 1)
{
throw new ProxyException("SOCKS5: Server authentication version is not valid.");
}
var statusCode = SocketReadByte(socket);
if (statusCode != 0)
{
throw new ProxyException("SOCKS5: Username/Password authentication failed.");
}
break;
case 0xFF:
throw new ProxyException("SOCKS5: No acceptable authentication methods were offered.");
default:
throw new ProxyException("SOCKS5: No acceptable authentication methods were offered.");
}
// Send socks version number
SocketWriteByte(socket, 0x05);
// Send command code
SocketWriteByte(socket, 0x01); // establish a TCP/IP stream connection
// Send reserved, must be 0x00
SocketWriteByte(socket, 0x00);
// Send address type and address
SocketAbstraction.Send(socket, addressBytes);
// Send port
SocketWriteByte(socket, (byte)(port / 0xFF));
SocketWriteByte(socket, (byte)(port % 0xFF));
// Read Server SOCKS5 version
if (SocketReadByte(socket) != 5)
{
throw new ProxyException("SOCKS5: Version 5 is expected.");
}
// Read response code
var status = SocketReadByte(socket);
switch (status)
{
case 0x00:
break;
case 0x01:
throw new ProxyException("SOCKS5: General failure.");
case 0x02:
throw new ProxyException("SOCKS5: Connection not allowed by ruleset.");
case 0x03:
throw new ProxyException("SOCKS5: Network unreachable.");
case 0x04:
throw new ProxyException("SOCKS5: Host unreachable.");
case 0x05:
throw new ProxyException("SOCKS5: Connection refused by destination host.");
case 0x06:
throw new ProxyException("SOCKS5: TTL expired.");
case 0x07:
throw new ProxyException("SOCKS5: Command not supported or protocol error.");
case 0x08:
throw new ProxyException("SOCKS5: Address type not supported.");
default:
throw new ProxyException("SOCKS4: Not valid response.");
}
// Read 0
if (SocketReadByte(socket) != 0)
{
throw new ProxyException("SOCKS5: 0 byte is expected.");
}
var addressType = SocketReadByte(socket);
var responseIp = new byte[16];
switch (addressType)
{
case 0x01:
SocketRead(socket, responseIp, 0, 4);
break;
case 0x04:
SocketRead(socket, responseIp, 0, 16);
break;
default:
throw new ProxyException(string.Format("Address type '{0}' is not supported.", addressType));
}
var portBytes = new byte[2];
// Read 2 bytes to be ignored
SocketRead(socket, portBytes, 0, 2);
return socket;
}
private static byte[] GetAddressBytes(IPEndPoint endPoint)
{
if (endPoint.AddressFamily == AddressFamily.InterNetwork)
{
var addressBytes = new byte[4 + 1];
addressBytes[0] = 0x01;
var address = endPoint.Address.GetAddressBytes();
Buffer.BlockCopy(address, 0, addressBytes, 1, address.Length);
return addressBytes;
}
if (endPoint.AddressFamily == AddressFamily.InterNetworkV6)
{
var addressBytes = new byte[16 + 1];
addressBytes[0] = 0x04;
var address = endPoint.Address.GetAddressBytes();
Buffer.BlockCopy(address, 0, addressBytes, 1, address.Length);
return addressBytes;
}
throw new ProxyException(string.Format("SOCKS5: IP address '{0}' is not supported.", endPoint.Address));
}
private static void SocketWriteByte(Socket socket, byte data)
{
SocketAbstraction.Send(socket, new[] { data });
}
private static byte SocketReadByte(Socket socket)
{
var buffer = new byte[1];
SocketRead(socket, buffer, 0, 1);
return buffer[0];
}
private static int SocketRead(Socket socket, byte[] buffer, int offset, int length)
{
var bytesRead = SocketAbstraction.Read(socket, buffer, offset, length, TimeSpan.FromMilliseconds(-1));
if (bytesRead == 0)
{
// when we're in the disconnecting state (either triggered by client or server), then the
// SshConnectionException will interrupt the message listener loop (if not already interrupted)
// and the exception itself will be ignored (in RaiseError)
throw new SshConnectionException("An established connection was aborted by the server.",
DisconnectReason.ConnectionLost);
}
return bytesRead;
}
}
}
@@ -0,0 +1,462 @@
using Renci.SshNet.Common;
using Renci.SshNet.IntegrationTests.Common;
using Renci.SshNet.Messages.Transport;
namespace Renci.SshNet.IntegrationTests
{
[TestClass]
public class ConnectivityTests : IntegrationTestBase
{
private AuthenticationMethodFactory _authenticationMethodFactory;
private IConnectionInfoFactory _connectionInfoFactory;
private IConnectionInfoFactory _adminConnectionInfoFactory;
private RemoteSshdConfig _remoteSshdConfig;
private SshConnectionDisruptor _sshConnectionDisruptor;
[TestInitialize]
public void SetUp()
{
_authenticationMethodFactory = new AuthenticationMethodFactory();
_connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort, _authenticationMethodFactory);
_adminConnectionInfoFactory = new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort);
_remoteSshdConfig = new RemoteSshd(_adminConnectionInfoFactory).OpenConfig();
_sshConnectionDisruptor = new SshConnectionDisruptor(_adminConnectionInfoFactory);
}
[TestCleanup]
public void TearDown()
{
_remoteSshdConfig?.Reset();
}
[TestMethod]
public void Common_CreateMoreChannelsThanMaxSessions()
{
var connectionInfo = _connectionInfoFactory.Create();
connectionInfo.MaxSessions = 2;
using (var client = new SshClient(connectionInfo))
{
client.Connect();
// create one more channel than the maximum number of sessions
// as that would block indefinitely when creating the last channel
// if the channel would not be properly closed
for (var i = 0; i < connectionInfo.MaxSessions + 1; i++)
{
using (var stream = client.CreateShellStream("vt220", 20, 20, 20, 20, 0))
{
stream.WriteLine("echo test");
stream.ReadLine();
}
}
}
}
[TestMethod]
public void Common_DisposeAfterLossOfNetworkConnectivity()
{
var hostNetworkConnectionDisabled = false;
SshConnectionRestorer disruptor = null;
try
{
Exception errorOccurred = null;
int count = 0;
using (var client = new SftpClient(_connectionInfoFactory.Create()))
{
client.ErrorOccurred += (sender, args) =>
{
Console.WriteLine("Exception " + count++);
Console.WriteLine(args.Exception);
errorOccurred = args.Exception;
};
client.Connect();
disruptor = _sshConnectionDisruptor.BreakConnections();
hostNetworkConnectionDisabled = true;
WaitForConnectionInterruption(client);
}
Assert.IsNotNull(errorOccurred);
Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
var connectionException = (SshConnectionException) errorOccurred;
Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason);
Assert.IsNull(connectionException.InnerException);
Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message);
}
finally
{
if (hostNetworkConnectionDisabled)
{
disruptor?.RestoreConnections();
disruptor?.Dispose();
}
}
}
[TestMethod]
public void Common_DetectLossOfNetworkConnectivityThroughKeepAlive()
{
using (var client = new SftpClient(_connectionInfoFactory.Create()))
{
Exception errorOccurred = null;
int count = 0;
client.ErrorOccurred += (sender, args) =>
{
Console.WriteLine("Exception "+ count++);
Console.WriteLine(args.Exception);
errorOccurred = args.Exception;
};
client.KeepAliveInterval = new TimeSpan(0, 0, 0, 0, 50);
client.Connect();
var disruptor = _sshConnectionDisruptor.BreakConnections();
try
{
WaitForConnectionInterruption(client);
Assert.IsFalse(client.IsConnected);
Assert.IsNotNull(errorOccurred);
Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
var connectionException = (SshConnectionException) errorOccurred;
Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason);
Assert.IsNull(connectionException.InnerException);
Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message);
}
finally
{
disruptor?.RestoreConnections();
disruptor?.Dispose();
}
}
}
private static void WaitForConnectionInterruption(SftpClient client)
{
for (var i = 0; i < 500; i++)
{
if (!client.IsConnected)
{
break;
}
Thread.Sleep(100);
}
// After interruption, you have to wait for the events to propagate.
Thread.Sleep(100);
}
[TestMethod]
public void Common_DetectConnectionResetThroughSftpInvocation()
{
using (var client = new SftpClient(_connectionInfoFactory.Create()))
{
client.KeepAliveInterval = TimeSpan.FromSeconds(1);
client.OperationTimeout = TimeSpan.FromSeconds(60);
ManualResetEvent errorOccurredSignaled = new ManualResetEvent(false);
Exception errorOccurred = null;
client.ErrorOccurred += (sender, args) =>
{
errorOccurred = args.Exception;
errorOccurredSignaled.Set();
};
client.Connect();
var disruptor = _sshConnectionDisruptor.BreakConnections();
try
{
client.ListDirectory("/");
Assert.Fail();
}
catch (SshConnectionException ex)
{
Assert.IsNull(ex.InnerException);
Assert.AreEqual("Client not connected.", ex.Message);
Assert.IsNotNull(errorOccurred);
Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
var connectionException = (SshConnectionException) errorOccurred;
Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason);
Assert.IsNull(connectionException.InnerException);
Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message);
}
finally
{
disruptor.RestoreConnections();
disruptor.Dispose();
}
}
}
[TestMethod]
public void Common_LossOfNetworkConnectivityDisconnectAndConnect()
{
bool vmNetworkConnectionDisabled = false;
SshConnectionRestorer disruptor = null;
try
{
using (var client = new SftpClient(_connectionInfoFactory.Create()))
{
Exception errorOccurred = null;
client.ErrorOccurred += (sender, args) => errorOccurred = args.Exception;
client.Connect();
disruptor = _sshConnectionDisruptor.BreakConnections();
vmNetworkConnectionDisabled = true;
WaitForConnectionInterruption(client);
// disconnect while network connectivity is lost
client.Disconnect();
Assert.IsFalse(client.IsConnected);
disruptor.RestoreConnections();
vmNetworkConnectionDisabled = false;
// connect when network connectivity is restored
client.Connect();
client.ChangeDirectory(client.WorkingDirectory);
client.Dispose();
Assert.IsNotNull(errorOccurred);
Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
var connectionException = (SshConnectionException) errorOccurred;
Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason);
Assert.IsNull(connectionException.InnerException);
Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message);
}
}
finally
{
if (vmNetworkConnectionDisabled)
{
disruptor.RestoreConnections();
}
disruptor?.Dispose();
}
}
[TestMethod]
public void Common_DetectLossOfNetworkConnectivityThroughSftpInvocation()
{
using (var client = new SftpClient(_connectionInfoFactory.Create()))
{
ManualResetEvent errorOccurredSignaled = new ManualResetEvent(false);
Exception errorOccurred = null;
client.ErrorOccurred += (sender, args) =>
{
errorOccurred = args.Exception;
errorOccurredSignaled.Set();
};
client.Connect();
var disruptor = _sshConnectionDisruptor.BreakConnections();
Thread.Sleep(100);
try
{
client.ListDirectory("/");
Assert.Fail();
}
catch (SshConnectionException ex)
{
Assert.IsNull(ex.InnerException);
Assert.AreEqual("Client not connected.", ex.Message);
Assert.IsNotNull(errorOccurred);
Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
var connectionException = (SshConnectionException) errorOccurred;
Assert.AreEqual(DisconnectReason.ConnectionLost, connectionException.DisconnectReason);
Assert.IsNull(connectionException.InnerException);
Assert.AreEqual("An established connection was aborted by the server.", connectionException.Message);
}
finally
{
disruptor.RestoreConnections();
disruptor.Dispose();
}
}
}
[TestMethod]
public void Common_DetectSessionKilledOnServer()
{
using (var client = new SftpClient(_connectionInfoFactory.Create()))
{
ManualResetEvent errorOccurredSignaled = new ManualResetEvent(false);
Exception errorOccurred = null;
client.ErrorOccurred += (sender, args) =>
{
errorOccurred = args.Exception;
errorOccurredSignaled.Set();
};
client.Connect();
// Kill the server session
using (var adminClient = new SshClient(_adminConnectionInfoFactory.Create()))
{
adminClient.Connect();
var command = $"sudo ps --no-headers -u {client.ConnectionInfo.Username} -f | grep \"{client.ConnectionInfo.Username}@notty\" | awk '{{print $2}}' | xargs sudo kill -9";
var sshCommand = adminClient.CreateCommand(command);
var result = sshCommand.Execute();
Assert.AreEqual(0, sshCommand.ExitStatus, sshCommand.Error);
}
Assert.IsTrue(errorOccurredSignaled.WaitOne(200));
Assert.IsNotNull(errorOccurred);
Assert.AreEqual(typeof(SshConnectionException), errorOccurred.GetType());
Assert.IsNull(errorOccurred.InnerException);
Assert.AreEqual("An established connection was aborted by the server.", errorOccurred.Message);
Assert.IsFalse(client.IsConnected);
}
}
[TestMethod]
public void Common_HostKeyValidation_Failure()
{
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.HostKeyReceived += (sender, e) => { e.CanTrust = false; };
try
{
client.Connect();
Assert.Fail();
}
catch (SshConnectionException ex)
{
Assert.IsNull(ex.InnerException);
Assert.AreEqual("Key exchange negotiation failed.", ex.Message);
}
}
}
[TestMethod]
public void Common_HostKeyValidation_Success()
{
byte[] host_rsa_key_openssh_fingerprint =
{
0x3d, 0x90, 0xd8, 0x0d, 0xd5, 0xe0, 0xb6, 0x13,
0x42, 0x7c, 0x78, 0x1e, 0x19, 0xa3, 0x99, 0x2b
};
var hostValidationSuccessful = false;
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.HostKeyReceived += (sender, e) =>
{
if (host_rsa_key_openssh_fingerprint.Length == e.FingerPrint.Length)
{
for (var i = 0; i < host_rsa_key_openssh_fingerprint.Length; i++)
{
if (host_rsa_key_openssh_fingerprint[i] != e.FingerPrint[i])
{
e.CanTrust = false;
break;
}
}
hostValidationSuccessful = e.CanTrust;
}
else
{
e.CanTrust = false;
}
};
client.Connect();
}
Assert.IsTrue(hostValidationSuccessful);
}
[TestMethod]
public void Common_HostKeyValidationSHA256_Success()
{
var hostValidationSuccessful = false;
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.HostKeyReceived += (sender, e) =>
{
if (e.FingerPrintSHA256 == "9fa6vbz64gimzsGZ/xZi3aaYE1o7E96iU2NjcfQNGwI")
{
hostValidationSuccessful = e.CanTrust;
}
else
{
e.CanTrust = false;
}
};
client.Connect();
}
Assert.IsTrue(hostValidationSuccessful);
}
[TestMethod]
public void Common_HostKeyValidationMD5_Success()
{
var hostValidationSuccessful = false;
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.HostKeyReceived += (sender, e) =>
{
if (e.FingerPrintMD5 == "3d:90:d8:0d:d5:e0:b6:13:42:7c:78:1e:19:a3:99:2b")
{
hostValidationSuccessful = e.CanTrust;
}
else
{
e.CanTrust = false;
}
};
client.Connect();
}
Assert.IsTrue(hostValidationSuccessful);
}
/// <summary>
/// Verifies whether we handle a disconnect initiated by the SSH server (through a SSH_MSG_DISCONNECT message).
/// </summary>
/// <remarks>
/// We force this by only configuring <c>keyboard-interactive</c> as authentication method, while <c>ChallengeResponseAuthentication</c>
/// is not enabled. This causes OpenSSH to terminate the connection because there are no authentication methods left.
/// </remarks>
[TestMethod]
public void Common_ServerRejectsConnection()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive")
.Update()
.Restart();
var connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegularUserKeyboardInteractiveAuthenticationMethod());
using (var client = new SftpClient(connectionInfo))
{
try
{
client.Connect();
Assert.Fail();
}
catch (SshConnectionException ex)
{
Assert.AreEqual(DisconnectReason.ProtocolError, ex.DisconnectReason);
Assert.IsNull(ex.InnerException);
Assert.AreEqual("The connection was closed by the server: no authentication methods enabled (ProtocolError).", ex.Message);
}
}
}
}
}
@@ -0,0 +1,14 @@
namespace Renci.SshNet.IntegrationTests
{
internal class Credential
{
public Credential(string userName, string password)
{
UserName = userName;
Password = password;
}
public string UserName { get; }
public string Password { get; }
}
}
@@ -0,0 +1,50 @@
FROM alpine:latest
COPY --chown=root:root server/ssh /etc/ssh/
COPY --chown=root:root server/script /opt/sshnet
COPY user/sshnet /home/sshnet/.ssh
RUN apk update && apk upgrade --no-cache && \
apk add --no-cache syslog-ng && \
# install and configure sshd
apk add --no-cache openssh && \
# install openssh-server-pam to allow for keyboard-interactive authentication
apk add --no-cache openssh-server-pam && \
dos2unix /etc/ssh/* && \
chmod 400 /etc/ssh/ssh*key && \
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
sed -i 's/#LogLevel\s*INFO/LogLevel DEBUG3/' /etc/ssh/sshd_config && \
# Set the default RSA key
echo 'HostKey /etc/ssh/ssh_host_rsa_key' >> /etc/ssh/sshd_config && \
chmod 646 /etc/ssh/sshd_config && \
# install and configure sudo
apk add --no-cache sudo && \
addgroup sudo && \
# allow root to run any command
echo 'root ALL=(ALL) ALL' > /etc/sudoers && \
# allow everyone in the 'sudo' group to run any command without a password
echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \
# add user to run most of the integration tests
adduser -D sshnet && \
passwd -u sshnet && \
echo 'sshnet:ssh4ever' | chpasswd && \
dos2unix /home/sshnet/.ssh/* && \
chown -R sshnet:sshnet /home/sshnet && \
chmod -R 700 /home/sshnet/.ssh && \
chmod -R 644 /home/sshnet/.ssh/authorized_keys && \
# add user to administer container (update configs, restart sshd)
adduser -D sshnetadm && \
passwd -u sshnetadm && \
echo 'sshnetadm:ssh4ever' | chpasswd && \
addgroup sshnetadm sudo && \
dos2unix /opt/sshnet/* && \
# install shadow package; we use chage command in this package to expire/unexpire password of the sshnet user
apk add --no-cache shadow && \
# allow us to use telnet command; we use this in the remote port forwarding tests
apk --no-cache add busybox-extras && \
# install full-fledged ps command
apk add --no-cache procps
EXPOSE 22 22
ENTRYPOINT ["/opt/sshnet/start.sh"]
@@ -0,0 +1,75 @@
using Renci.SshNet.IntegrationTests.Common;
using Renci.SshNet.TestTools.OpenSSH;
namespace Renci.SshNet.IntegrationTests
{
[TestClass]
public class HmacTests : IntegrationTestBase
{
private IConnectionInfoFactory _connectionInfoFactory;
private RemoteSshdConfig _remoteSshdConfig;
[TestInitialize]
public void SetUp()
{
_connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort);
_remoteSshdConfig = new RemoteSshd(new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort)).OpenConfig();
}
[TestCleanup]
public void TearDown()
{
_remoteSshdConfig?.Reset();
}
[TestMethod]
public void HmacMd5()
{
DoTest(MessageAuthenticationCodeAlgorithm.HmacMd5);
}
[TestMethod]
public void HmacMd5_96()
{
DoTest(MessageAuthenticationCodeAlgorithm.HmacMd5_96);
}
[TestMethod]
public void HmacSha1()
{
DoTest(MessageAuthenticationCodeAlgorithm.HmacSha1);
}
[TestMethod]
public void HmacSha1_96()
{
DoTest(MessageAuthenticationCodeAlgorithm.HmacSha1_96);
}
[TestMethod]
public void HmacSha2_256()
{
DoTest(MessageAuthenticationCodeAlgorithm.HmacSha2_256);
}
[TestMethod]
public void HmacSha2_512()
{
DoTest(MessageAuthenticationCodeAlgorithm.HmacSha2_512);
}
private void DoTest(MessageAuthenticationCodeAlgorithm macAlgorithm)
{
_remoteSshdConfig.ClearMessageAuthenticationCodeAlgorithms()
.AddMessageAuthenticationCodeAlgorithm(macAlgorithm)
.Update()
.Restart();
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
client.Disconnect();
}
}
}
}
@@ -0,0 +1,115 @@
using System.Net;
using System.Text.RegularExpressions;
namespace Renci.SshNet.IntegrationTests
{
class HostConfig
{
private static readonly Regex HostsEntryRegEx = new Regex(@"^(?<IPAddress>[\S]+)\s+(?<HostName>[a-zA-Z]+[a-zA-Z\-\.]*[a-zA-Z]+)\s*(?<Aliases>.+)*$", RegexOptions.Singleline);
public List<HostEntry> Entries { get; }
private HostConfig()
{
Entries = new List<HostEntry>();
}
public static HostConfig Read(ScpClient scpClient, string path)
{
HostConfig hostConfig = new HostConfig();
using (var ms = new MemoryStream())
{
scpClient.Download(path, ms);
ms.Position = 0;
using (var sr = new StreamReader(ms, Encoding.ASCII))
{
string line;
while ((line = sr.ReadLine()) != null)
{
// skip comments
if (line.StartsWith("#"))
{
continue;
}
var hostEntryMatch = HostsEntryRegEx.Match(line);
if (!hostEntryMatch.Success)
{
continue;
}
var entryIPAddress = hostEntryMatch.Groups["IPAddress"].Value;
var entryAliasesGroup = hostEntryMatch.Groups["Aliases"];
var entry = new HostEntry(IPAddress.Parse(entryIPAddress), hostEntryMatch.Groups["HostName"].Value);
if (entryAliasesGroup.Success)
{
var aliases = entryAliasesGroup.Value.Split(' ');
foreach (var alias in aliases)
{
entry.Aliases.Add(alias);
}
}
hostConfig.Entries.Add(entry);
}
}
}
return hostConfig;
}
public void Write(ScpClient scpClient, string path)
{
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms, Encoding.ASCII))
{
// Use linux line ending
sw.NewLine = "\n";
foreach (var hostEntry in Entries)
{
sw.Write(hostEntry.IPAddress);
sw.Write(" ");
sw.Write(hostEntry.HostName);
if (hostEntry.Aliases.Count > 0)
{
sw.Write(" ");
for (var i = 0; i < hostEntry.Aliases.Count; i++)
{
if (i > 0)
{
sw.Write(' ');
}
sw.Write(hostEntry.Aliases[i]);
}
}
sw.WriteLine();
}
sw.Flush();
ms.Position = 0;
scpClient.Upload(ms, path);
}
}
}
public class HostEntry
{
public HostEntry(IPAddress ipAddress, string hostName)
{
IPAddress = ipAddress;
HostName = hostName;
Aliases = new List<string>();
}
public IPAddress IPAddress { get; private set; }
public string HostName { get; set; }
public List<string> Aliases { get; }
}
}
@@ -0,0 +1,80 @@
using Renci.SshNet.Common;
using Renci.SshNet.IntegrationTests.Common;
using Renci.SshNet.TestTools.OpenSSH;
namespace Renci.SshNet.IntegrationTests
{
[TestClass]
public class HostKeyAlgorithmTests : IntegrationTestBase
{
private IConnectionInfoFactory _connectionInfoFactory;
private RemoteSshdConfig _remoteSshdConfig;
[TestInitialize]
public void SetUp()
{
_connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort);
_remoteSshdConfig = new RemoteSshd(new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort)).OpenConfig();
}
[TestCleanup]
public void TearDown()
{
_remoteSshdConfig?.Reset();
}
[TestMethod]
public void SshDss()
{
DoTest(HostKeyAlgorithm.SshDss, HostKeyFile.Dsa, 2048);
}
[TestMethod]
public void SshRsa()
{
DoTest(HostKeyAlgorithm.SshRsa, HostKeyFile.Rsa, 3072);
}
[TestMethod]
public void SshRsaSha256()
{
DoTest(HostKeyAlgorithm.RsaSha2256, HostKeyFile.Rsa, 3072);
}
[TestMethod]
public void SshRsaSha512()
{
DoTest(HostKeyAlgorithm.RsaSha2512, HostKeyFile.Rsa, 3072);
}
[TestMethod]
public void SshEd25519()
{
DoTest(HostKeyAlgorithm.SshEd25519, HostKeyFile.Ed25519, 256);
}
private void DoTest(HostKeyAlgorithm hostKeyAlgorithm, HostKeyFile hostKeyFile, int keyLength)
{
_remoteSshdConfig.ClearHostKeyAlgorithms()
.AddHostKeyAlgorithm(hostKeyAlgorithm)
.ClearHostKeyFiles()
.AddHostKeyFile(hostKeyFile.FilePath)
.Update()
.Restart();
HostKeyEventArgs hostKeyEventsArgs = null;
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.HostKeyReceived += (sender, e) => hostKeyEventsArgs = e;
client.Connect();
client.Disconnect();
}
Assert.IsNotNull(hostKeyEventsArgs);
Assert.AreEqual(hostKeyAlgorithm.Name, hostKeyEventsArgs.HostKeyName);
Assert.AreEqual(keyLength, hostKeyEventsArgs.KeyLength);
CollectionAssert.AreEqual(hostKeyFile.FingerPrint, hostKeyEventsArgs.FingerPrint);
}
}
}
@@ -0,0 +1,23 @@
namespace Renci.SshNet.IntegrationTests
{
public sealed class HostKeyFile
{
public static readonly HostKeyFile Rsa = new HostKeyFile("ssh-rsa", "/etc/ssh/ssh_host_rsa_key", new byte[] { 0x3d, 0x90, 0xd8, 0x0d, 0xd5, 0xe0, 0xb6, 0x13, 0x42, 0x7c, 0x78, 0x1e, 0x19, 0xa3, 0x99, 0x2b });
public static readonly HostKeyFile Dsa = new HostKeyFile("ssh-dsa", "/etc/ssh/ssh_host_dsa_key", new byte[] { 0x50, 0xe0, 0xd5, 0x11, 0xf7, 0xed, 0x54, 0x75, 0x0d, 0x03, 0xc6, 0x52, 0x9b, 0x3b, 0x3c, 0x9f });
public static readonly HostKeyFile Ed25519 = new HostKeyFile("ssh-ed25519", "/etc/ssh/ssh_host_ed25519_key", new byte[] { 0xb3, 0xb9, 0xd0, 0x1b, 0x73, 0xc4, 0x60, 0xb4, 0xce, 0xed, 0x06, 0xf8, 0x58, 0x49, 0xa3, 0xda });
public const string Ecdsa = "/etc/ssh/ssh_host_ecdsa_key";
private HostKeyFile(string keyName, string filePath, byte[] fingerPrint)
{
KeyName = keyName;
FilePath = filePath;
FingerPrint = fingerPrint;
}
public string KeyName {get; }
public string FilePath { get; }
public byte[] FingerPrint { get; }
}
}
@@ -0,0 +1,10 @@
namespace Renci.SshNet.IntegrationTests
{
public interface IConnectionInfoFactory
{
ConnectionInfo Create();
ConnectionInfo Create(params AuthenticationMethod[] authenticationMethods);
ConnectionInfo CreateWithProxy();
ConnectionInfo CreateWithProxy(params AuthenticationMethod[] authenticationMethods);
}
}
@@ -0,0 +1,206 @@
using Renci.SshNet.IntegrationTests.Common;
using Renci.SshNet.TestTools.OpenSSH;
namespace Renci.SshNet.IntegrationTests
{
[TestClass]
public class KeyExchangeAlgorithmTests : IntegrationTestBase
{
private IConnectionInfoFactory _connectionInfoFactory;
private RemoteSshdConfig _remoteSshdConfig;
[TestInitialize]
public void SetUp()
{
_connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort);
_remoteSshdConfig = new RemoteSshd(new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort)).OpenConfig();
}
[TestCleanup]
public void TearDown()
{
_remoteSshdConfig?.Reset();
}
[TestMethod]
public void Curve25519Sha256()
{
_remoteSshdConfig.ClearKeyExchangeAlgorithms()
.AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.Curve25519Sha256)
.Update()
.Restart();
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
client.Disconnect();
}
}
[TestMethod]
public void Curve25519Sha256Libssh()
{
_remoteSshdConfig.ClearKeyExchangeAlgorithms()
.AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.Curve25519Sha256Libssh)
.Update()
.Restart();
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
client.Disconnect();
}
}
[TestMethod]
public void DiffieHellmanGroup1Sha1()
{
_remoteSshdConfig.ClearKeyExchangeAlgorithms()
.AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroup1Sha1)
.Update()
.Restart();
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
client.Disconnect();
}
}
[TestMethod]
public void DiffieHellmanGroup14Sha1()
{
_remoteSshdConfig.ClearKeyExchangeAlgorithms()
.AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroup14Sha1)
.Update()
.Restart();
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
client.Disconnect();
}
}
[TestMethod]
public void DiffieHellmanGroup14Sha256()
{
_remoteSshdConfig.ClearKeyExchangeAlgorithms()
.AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroup14Sha256)
.Update()
.Restart();
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
client.Disconnect();
}
}
[TestMethod]
public void DiffieHellmanGroup16Sha512()
{
_remoteSshdConfig.ClearKeyExchangeAlgorithms()
.AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroup16Sha512)
.Update()
.Restart();
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
client.Disconnect();
}
}
[TestMethod]
[Ignore]
public void DiffieHellmanGroup18Sha512()
{
_remoteSshdConfig.ClearKeyExchangeAlgorithms()
.AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroup18Sha512)
.Update()
.Restart();
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
client.Disconnect();
}
}
[TestMethod]
public void DiffieHellmanGroupExchangeSha1()
{
_remoteSshdConfig.ClearKeyExchangeAlgorithms()
.AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroupExchangeSha1)
.Update()
.Restart();
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
client.Disconnect();
}
}
[TestMethod]
public void DiffieHellmanGroupExchangeSha256()
{
_remoteSshdConfig.ClearKeyExchangeAlgorithms()
.AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.DiffieHellmanGroupExchangeSha256)
.Update()
.Restart();
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
client.Disconnect();
}
}
[TestMethod]
public void EcdhSha2Nistp256()
{
_remoteSshdConfig.ClearKeyExchangeAlgorithms()
.AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.EcdhSha2Nistp256)
.Update()
.Restart();
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
client.Disconnect();
}
}
[TestMethod]
public void EcdhSha2Nistp384()
{
_remoteSshdConfig.ClearKeyExchangeAlgorithms()
.AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.EcdhSha2Nistp384)
.Update()
.Restart();
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
client.Disconnect();
}
}
[TestMethod]
public void EcdhSha2Nistp521()
{
_remoteSshdConfig.ClearKeyExchangeAlgorithms()
.AddKeyExchangeAlgorithm(KeyExchangeAlgorithm.EcdhSha2Nistp521)
.Update()
.Restart();
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
client.Disconnect();
}
}
}
}
@@ -0,0 +1,36 @@
namespace Renci.SshNet.IntegrationTests
{
public class LinuxAdminConnectionFactory : IConnectionInfoFactory
{
private readonly string _host;
private readonly int _port;
public LinuxAdminConnectionFactory(string sshServerHostName, ushort sshServerPort)
{
_host = sshServerHostName;
_port = sshServerPort;
}
public ConnectionInfo Create()
{
var user = Users.Admin;
return new ConnectionInfo(_host, _port, user.UserName, new PasswordAuthenticationMethod(user.UserName, user.Password));
}
public ConnectionInfo Create(params AuthenticationMethod[] authenticationMethods)
{
throw new NotImplementedException();
}
public ConnectionInfo CreateWithProxy()
{
throw new NotImplementedException();
}
public ConnectionInfo CreateWithProxy(params AuthenticationMethod[] authenticationMethods)
{
throw new NotImplementedException();
}
}
}
@@ -0,0 +1,62 @@
namespace Renci.SshNet.IntegrationTests
{
public class LinuxVMConnectionFactory : IConnectionInfoFactory
{
private const string ProxyHost = "127.0.0.1";
private const int ProxyPort = 1234;
private const string ProxyUserName = "test";
private const string ProxyPassword = "123";
private readonly string _host;
private readonly int _port;
private readonly AuthenticationMethodFactory _authenticationMethodFactory;
public LinuxVMConnectionFactory(string sshServerHostName, ushort sshServerPort)
{
_host = sshServerHostName;
_port = sshServerPort;
_authenticationMethodFactory = new AuthenticationMethodFactory();
}
public LinuxVMConnectionFactory(string sshServerHostName, ushort sshServerPort, AuthenticationMethodFactory authenticationMethodFactory)
{
_host = sshServerHostName;
_port = sshServerPort;
_authenticationMethodFactory = authenticationMethodFactory;
}
public ConnectionInfo Create()
{
return Create(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod());
}
public ConnectionInfo Create(params AuthenticationMethod[] authenticationMethods)
{
return new ConnectionInfo(_host, _port, Users.Regular.UserName, authenticationMethods);
}
public ConnectionInfo CreateWithProxy()
{
return CreateWithProxy(_authenticationMethodFactory.CreateRegularUserPrivateKeyAuthenticationMethod());
}
public ConnectionInfo CreateWithProxy(params AuthenticationMethod[] authenticationMethods)
{
return new ConnectionInfo(
_host,
_port,
Users.Regular.UserName,
ProxyTypes.Socks4,
ProxyHost,
ProxyPort,
ProxyUserName,
ProxyPassword,
authenticationMethods);
}
}
}
@@ -0,0 +1,158 @@
using System.Diagnostics;
using Renci.SshNet.Common;
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
/// <summary>
/// Provides functionality for local port forwarding
/// </summary>
[TestClass]
public class ForwardedPortLocalTest : IntegrationTestBase
{
[TestMethod]
[WorkItem(713)]
[Owner("Kenneth_aa")]
[TestCategory("PortForwarding")]
[Description("Test if calling Stop on ForwardedPortLocal instance causes wait.")]
public void Test_PortForwarding_Local_Stop_Hangs_On_Wait()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
var port1 = new ForwardedPortLocal("localhost", 8084, "www.google.com", 80);
client.AddForwardedPort(port1);
port1.Exception += delegate (object sender, ExceptionEventArgs e)
{
Assert.Fail(e.Exception.ToString());
};
port1.Start();
var hasTestedTunnel = false;
_ = ThreadPool.QueueUserWorkItem(delegate (object state)
{
try
{
var url = "http://www.google.com/";
Debug.WriteLine("Starting web request to \"" + url + "\"");
#if NET6_0_OR_GREATER
var httpClient = new HttpClient();
var response = httpClient.GetAsync(url)
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
#else
var request = (HttpWebRequest) WebRequest.Create(url);
var response = (HttpWebResponse) request.GetResponse();
#endif // NET6_0_OR_GREATER
Assert.IsNotNull(response);
Debug.WriteLine("Http Response status code: " + response.StatusCode.ToString());
response.Dispose();
hasTestedTunnel = true;
}
catch (Exception ex)
{
Assert.Fail(ex.ToString());
}
});
// Wait for the web request to complete.
while (!hasTestedTunnel)
{
Thread.Sleep(1000);
}
try
{
// Try stop the port forwarding, wait 3 seconds and fail if it is still started.
_ = ThreadPool.QueueUserWorkItem(delegate (object state)
{
Debug.WriteLine("Trying to stop port forward.");
port1.Stop();
Debug.WriteLine("Port forwarding stopped.");
});
Thread.Sleep(3000);
if (port1.IsStarted)
{
Assert.Fail("Port forwarding not stopped.");
}
}
catch (Exception ex)
{
Assert.Fail(ex.ToString());
}
client.RemoveForwardedPort(port1);
client.Disconnect();
Debug.WriteLine("Success.");
}
}
[TestMethod]
[ExpectedException(typeof(SshConnectionException))]
public void Test_PortForwarding_Local_Without_Connecting()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
var port1 = new ForwardedPortLocal("localhost", 8084, "www.renci.org", 80);
client.AddForwardedPort(port1);
port1.Exception += delegate (object sender, ExceptionEventArgs e)
{
Assert.Fail(e.Exception.ToString());
};
port1.Start();
var test = Parallel.For(0,
100,
counter =>
{
var start = DateTime.Now;
#if NET6_0_OR_GREATER
var httpClient = new HttpClient();
using (var response = httpClient.GetAsync("http://localhost:8084").GetAwaiter().GetResult())
{
var data = ReadStream(response.Content.ReadAsStream());
#else
var request = (HttpWebRequest) WebRequest.Create("http://localhost:8084");
using (var response = (HttpWebResponse) request.GetResponse())
{
var data = ReadStream(response.GetResponseStream());
#endif // NET6_0_OR_GREATER
var end = DateTime.Now;
Debug.WriteLine(string.Format("Request# {2}: Lenght: {0} Time: {1}", data.Length, end - start, counter));
}
});
}
}
private static byte[] ReadStream(Stream stream)
{
var buffer = new byte[1024];
using (var ms = new MemoryStream())
{
while (true)
{
var read = stream.Read(buffer, 0, buffer.Length);
if (read > 0)
{
ms.Write(buffer, 0, read);
}
else
{
return ms.ToArray();
}
}
}
}
}
}
@@ -0,0 +1,336 @@
using System.Security.Cryptography;
using Renci.SshNet.Common;
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
/// <summary>
/// Provides SCP client functionality.
/// </summary>
[TestClass]
public partial class ScpClientTest : IntegrationTestBase
{
[TestMethod]
[TestCategory("Scp")]
public void Test_Scp_File_Upload_Download()
{
RemoveAllFiles();
using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
scp.Connect();
var uploadedFileName = Path.GetTempFileName();
var downloadedFileName = Path.GetTempFileName();
CreateTestFile(uploadedFileName, 1);
scp.Upload(new FileInfo(uploadedFileName), Path.GetFileName(uploadedFileName));
scp.Download(Path.GetFileName(uploadedFileName), new FileInfo(downloadedFileName));
// Calculate MD5 value
var uploadedHash = CalculateMD5(uploadedFileName);
var downloadedHash = CalculateMD5(downloadedFileName);
File.Delete(uploadedFileName);
File.Delete(downloadedFileName);
scp.Disconnect();
Assert.AreEqual(uploadedHash, downloadedHash);
}
}
[TestMethod]
[TestCategory("Scp")]
public void Test_Scp_Stream_Upload_Download()
{
RemoveAllFiles();
using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
scp.Connect();
var uploadedFileName = Path.GetTempFileName();
var downloadedFileName = Path.GetTempFileName();
CreateTestFile(uploadedFileName, 1);
// Calculate has value
using (var stream = File.OpenRead(uploadedFileName))
{
scp.Upload(stream, Path.GetFileName(uploadedFileName));
}
using (var stream = File.OpenWrite(downloadedFileName))
{
scp.Download(Path.GetFileName(uploadedFileName), stream);
}
// Calculate MD5 value
var uploadedHash = CalculateMD5(uploadedFileName);
var downloadedHash = CalculateMD5(downloadedFileName);
File.Delete(uploadedFileName);
File.Delete(downloadedFileName);
scp.Disconnect();
Assert.AreEqual(uploadedHash, downloadedHash);
}
}
[TestMethod]
[TestCategory("Scp")]
public void Test_Scp_10MB_File_Upload_Download()
{
RemoveAllFiles();
using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
scp.Connect();
var uploadedFileName = Path.GetTempFileName();
var downloadedFileName = Path.GetTempFileName();
CreateTestFile(uploadedFileName, 10);
scp.Upload(new FileInfo(uploadedFileName), Path.GetFileName(uploadedFileName));
scp.Download(Path.GetFileName(uploadedFileName), new FileInfo(downloadedFileName));
// Calculate MD5 value
var uploadedHash = CalculateMD5(uploadedFileName);
var downloadedHash = CalculateMD5(downloadedFileName);
File.Delete(uploadedFileName);
File.Delete(downloadedFileName);
scp.Disconnect();
Assert.AreEqual(uploadedHash, downloadedHash);
}
}
[TestMethod]
[TestCategory("Scp")]
public void Test_Scp_10MB_Stream_Upload_Download()
{
RemoveAllFiles();
using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
scp.Connect();
var uploadedFileName = Path.GetTempFileName();
var downloadedFileName = Path.GetTempFileName();
CreateTestFile(uploadedFileName, 10);
// Calculate has value
using (var stream = File.OpenRead(uploadedFileName))
{
scp.Upload(stream, Path.GetFileName(uploadedFileName));
}
using (var stream = File.OpenWrite(downloadedFileName))
{
scp.Download(Path.GetFileName(uploadedFileName), stream);
}
// Calculate MD5 value
var uploadedHash = CalculateMD5(uploadedFileName);
var downloadedHash = CalculateMD5(downloadedFileName);
File.Delete(uploadedFileName);
File.Delete(downloadedFileName);
scp.Disconnect();
Assert.AreEqual(uploadedHash, downloadedHash);
}
}
[TestMethod]
[TestCategory("Scp")]
public void Test_Scp_Directory_Upload_Download()
{
RemoveAllFiles();
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.CreateDirectory("uploaded_dir");
}
using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
scp.Connect();
var uploadDirectory =
Directory.CreateDirectory(string.Format("{0}\\{1}", Path.GetTempPath(), Path.GetRandomFileName()));
for (var i = 0; i < 3; i++)
{
var subfolder = Directory.CreateDirectory(string.Format(@"{0}\folder_{1}", uploadDirectory.FullName, i));
for (var j = 0; j < 5; j++)
{
CreateTestFile(string.Format(@"{0}\file_{1}", subfolder.FullName, j), 1);
}
CreateTestFile(string.Format(@"{0}\file_{1}", uploadDirectory.FullName, i), 1);
}
scp.Upload(uploadDirectory, "uploaded_dir");
var downloadDirectory =
Directory.CreateDirectory(string.Format("{0}\\{1}", Path.GetTempPath(), Path.GetRandomFileName()));
scp.Download("uploaded_dir", downloadDirectory);
var uploadedFiles = uploadDirectory.GetFiles("*.*", SearchOption.AllDirectories);
var downloadFiles = downloadDirectory.GetFiles("*.*", SearchOption.AllDirectories);
var result = from f1 in uploadedFiles
from f2 in downloadFiles
where
f1.FullName.Substring(uploadDirectory.FullName.Length) ==
f2.FullName.Substring(downloadDirectory.FullName.Length)
&& CalculateMD5(f1.FullName) == CalculateMD5(f2.FullName)
select f1;
var counter = result.Count();
scp.Disconnect();
Assert.IsTrue(counter == uploadedFiles.Length && uploadedFiles.Length == downloadFiles.Length);
}
RemoveAllFiles();
}
[TestMethod]
[TestCategory("Scp")]
public void Test_Scp_File_20_Parallel_Upload_Download()
{
using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
scp.Connect();
var uploadFilenames = new string[20];
for (var i = 0; i < uploadFilenames.Length; i++)
{
uploadFilenames[i] = Path.GetTempFileName();
CreateTestFile(uploadFilenames[i], 1);
}
_ = Parallel.ForEach(uploadFilenames,
filename =>
{
scp.Upload(new FileInfo(filename), Path.GetFileName(filename));
});
_ = Parallel.ForEach(uploadFilenames,
filename =>
{
scp.Download(Path.GetFileName(filename), new FileInfo(string.Format("{0}.down", filename)));
});
var result = from file in uploadFilenames
where CalculateMD5(file) == CalculateMD5(string.Format("{0}.down", file))
select file;
scp.Disconnect();
Assert.IsTrue(result.Count() == uploadFilenames.Length);
}
}
[TestMethod]
[TestCategory("Scp")]
public void Test_Scp_File_Upload_Download_Events()
{
using (var scp = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
scp.Connect();
var uploadFilenames = new string[10];
for (var i = 0; i < uploadFilenames.Length; i++)
{
uploadFilenames[i] = Path.GetTempFileName();
CreateTestFile(uploadFilenames[i], 1);
}
var uploadedFiles = uploadFilenames.ToDictionary(Path.GetFileName, (filename) => 0L);
var downloadedFiles = uploadFilenames.ToDictionary((filename) => string.Format("{0}.down", Path.GetFileName(filename)), (filename) => 0L);
scp.Uploading += delegate (object sender, ScpUploadEventArgs e)
{
uploadedFiles[e.Filename] = e.Uploaded;
};
scp.Downloading += delegate (object sender, ScpDownloadEventArgs e)
{
downloadedFiles[string.Format("{0}.down", e.Filename)] = e.Downloaded;
};
_ = Parallel.ForEach(uploadFilenames,
filename =>
{
scp.Upload(new FileInfo(filename), Path.GetFileName(filename));
});
_ = Parallel.ForEach(uploadFilenames,
filename =>
{
scp.Download(Path.GetFileName(filename), new FileInfo(string.Format("{0}.down", filename)));
});
var result = from uf in uploadedFiles
from df in downloadedFiles
where string.Format("{0}.down", uf.Key) == df.Key && uf.Value == df.Value
select uf;
scp.Disconnect();
Assert.IsTrue(result.Count() == uploadFilenames.Length && uploadFilenames.Length == uploadedFiles.Count && uploadedFiles.Count == downloadedFiles.Count);
}
}
protected static string CalculateMD5(string fileName)
{
using (var file = new FileStream(fileName, FileMode.Open))
{
#if NET7_0_OR_GREATER
var hash = MD5.HashData(file);
#else
#if NET6_0
var md5 = MD5.Create();
#else
MD5 md5 = new MD5CryptoServiceProvider();
#endif // NET6_0
var hash = md5.ComputeHash(file);
#endif // NET7_0_OR_GREATER
file.Close();
var sb = new StringBuilder();
for (var i = 0; i < hash.Length; i++)
{
_ = sb.Append(i.ToString("x2"));
}
return sb.ToString();
}
}
private void RemoveAllFiles()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
_ = client.RunCommand("rm -rf *");
client.Disconnect();
}
}
}
}
@@ -1,21 +1,18 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Renci.SshNet.Common;
using Renci.SshNet.Tests.Properties;
using Renci.SshNet.Common;
namespace Renci.SshNet.Tests.Classes
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
/// <summary>
/// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
/// </summary>
public partial class SftpClientTest
public partial class SftpClientTest : IntegrationTestBase
{
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[ExpectedException(typeof(SftpPathNotFoundException))]
public void Test_Sftp_ChangeDirectory_Root_Dont_Exists()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.ChangeDirectory("/asdasd");
@@ -24,11 +21,10 @@ namespace Renci.SshNet.Tests.Classes
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[ExpectedException(typeof(SftpPathNotFoundException))]
public void Test_Sftp_ChangeDirectory_Root_With_Slash_Dont_Exists()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.ChangeDirectory("/asdasd/");
@@ -37,11 +33,10 @@ namespace Renci.SshNet.Tests.Classes
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[ExpectedException(typeof(SftpPathNotFoundException))]
public void Test_Sftp_ChangeDirectory_Subfolder_Dont_Exists()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.ChangeDirectory("/asdasd/sssddds");
@@ -50,11 +45,10 @@ namespace Renci.SshNet.Tests.Classes
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[ExpectedException(typeof(SftpPathNotFoundException))]
public void Test_Sftp_ChangeDirectory_Subfolder_With_Slash_Dont_Exists()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.ChangeDirectory("/asdasd/sssddds/");
@@ -63,10 +57,9 @@ namespace Renci.SshNet.Tests.Classes
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
public void Test_Sftp_ChangeDirectory_Which_Exists()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.ChangeDirectory("/usr");
@@ -76,10 +69,9 @@ namespace Renci.SshNet.Tests.Classes
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
public void Test_Sftp_ChangeDirectory_Which_Exists_With_Slash()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.ChangeDirectory("/usr/");
@@ -87,4 +79,4 @@ namespace Renci.SshNet.Tests.Classes
}
}
}
}
}
@@ -0,0 +1,72 @@
using Renci.SshNet.Common;
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
/// <summary>
/// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
/// </summary>
public partial class SftpClientTest : IntegrationTestBase
{
[TestMethod]
[TestCategory("Sftp")]
public void Test_Sftp_CreateDirectory_In_Current_Location()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.CreateDirectory("test-in-current");
sftp.Disconnect();
}
}
[TestMethod]
[TestCategory("Sftp")]
[ExpectedException(typeof(SftpPermissionDeniedException))]
public void Test_Sftp_CreateDirectory_In_Forbidden_Directory()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, AdminUser.UserName, AdminUser.Password))
{
sftp.Connect();
sftp.CreateDirectory("/sbin/test");
sftp.Disconnect();
}
}
[TestMethod]
[TestCategory("Sftp")]
[ExpectedException(typeof(SftpPathNotFoundException))]
public void Test_Sftp_CreateDirectory_Invalid_Path()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.CreateDirectory("/abcdefg/abcefg");
sftp.Disconnect();
}
}
[TestMethod]
[TestCategory("Sftp")]
[ExpectedException(typeof(SshException))]
public void Test_Sftp_CreateDirectory_Already_Exists()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.CreateDirectory("test");
sftp.CreateDirectory("test");
sftp.Disconnect();
}
}
}
}
@@ -0,0 +1,71 @@
using Renci.SshNet.Common;
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
/// <summary>
/// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
/// </summary>
public partial class SftpClientTest : IntegrationTestBase
{
[TestMethod]
[TestCategory("Sftp")]
[ExpectedException(typeof(SftpPathNotFoundException))]
public void Test_Sftp_DeleteDirectory_Which_Doesnt_Exists()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.DeleteDirectory("abcdef");
sftp.Disconnect();
}
}
[TestMethod]
[TestCategory("Sftp")]
[ExpectedException(typeof(SftpPermissionDeniedException))]
public void Test_Sftp_DeleteDirectory_Which_No_Permissions()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, AdminUser.UserName, AdminUser.Password))
{
sftp.Connect();
sftp.DeleteDirectory("/usr");
sftp.Disconnect();
}
}
[TestMethod]
[TestCategory("Sftp")]
public void Test_Sftp_DeleteDirectory()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.CreateDirectory("abcdef");
sftp.DeleteDirectory("abcdef");
sftp.Disconnect();
}
}
[TestMethod]
[TestCategory("Sftp")]
[Description("Test passing null to DeleteDirectory.")]
[ExpectedException(typeof(ArgumentException))]
public void Test_Sftp_DeleteDirectory_Null()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.DeleteDirectory(null);
sftp.Disconnect();
}
}
}
}
@@ -1,26 +1,18 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Renci.SshNet.Common;
using Renci.SshNet.Tests.Properties;
using System;
using System.IO;
using Renci.SshNet.Common;
namespace Renci.SshNet.Tests.Classes
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
/// <summary>
/// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
/// </summary>
public partial class SftpClientTest
public partial class SftpClientTest : IntegrationTestBase
{
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[ExpectedException(typeof(SftpPermissionDeniedException))]
public void Test_Sftp_Download_Forbidden()
{
if (Resources.USERNAME == "root")
Assert.Fail("Must not run this test as root!");
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, AdminUser.UserName, AdminUser.Password))
{
sftp.Connect();
@@ -37,11 +29,10 @@ namespace Renci.SshNet.Tests.Classes
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[ExpectedException(typeof(SftpPathNotFoundException))]
public void Test_Sftp_Download_File_Not_Exists()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
@@ -57,12 +48,11 @@ namespace Renci.SshNet.Tests.Classes
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[Description("Test passing null to BeginDownloadFile")]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_Sftp_BeginDownloadFile_StreamIsNull()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.BeginDownloadFile("aaaa", null, null, null);
@@ -72,12 +62,11 @@ namespace Renci.SshNet.Tests.Classes
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[Description("Test passing null to BeginDownloadFile")]
[ExpectedException(typeof(ArgumentException))]
public void Test_Sftp_BeginDownloadFile_FileNameIsWhiteSpace()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.BeginDownloadFile(" ", new MemoryStream(), null, null);
@@ -87,12 +76,11 @@ namespace Renci.SshNet.Tests.Classes
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[Description("Test passing null to BeginDownloadFile")]
[ExpectedException(typeof(ArgumentException))]
public void Test_Sftp_BeginDownloadFile_FileNameIsNull()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.BeginDownloadFile(null, new MemoryStream(), null, null);
@@ -102,15 +90,14 @@ namespace Renci.SshNet.Tests.Classes
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[ExpectedException(typeof(ArgumentException))]
public void Test_Sftp_EndDownloadFile_Invalid_Async_Handle()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
var filename = Path.GetTempFileName();
this.CreateTestFile(filename, 1);
CreateTestFile(filename, 1);
sftp.UploadFile(File.OpenRead(filename), "test123");
var async1 = sftp.BeginListDirectory("/", null, null);
var async2 = sftp.BeginDownloadFile("test123", new MemoryStream(), null, null);
@@ -118,4 +105,4 @@ namespace Renci.SshNet.Tests.Classes
}
}
}
}
}
@@ -0,0 +1,263 @@
using System.Diagnostics;
using Renci.SshNet.Common;
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
/// <summary>
/// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
/// </summary>
public partial class SftpClientTest : IntegrationTestBase
{
[TestMethod]
[TestCategory("Sftp")]
[ExpectedException(typeof(SftpPermissionDeniedException))]
public void Test_Sftp_ListDirectory_Permission_Denied()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
var files = sftp.ListDirectory("/root");
foreach (var file in files)
{
Debug.WriteLine(file.FullName);
}
sftp.Disconnect();
}
}
[TestMethod]
[TestCategory("Sftp")]
[ExpectedException(typeof(SftpPathNotFoundException))]
public void Test_Sftp_ListDirectory_Not_Exists()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
var files = sftp.ListDirectory("/asdfgh");
foreach (var file in files)
{
Debug.WriteLine(file.FullName);
}
sftp.Disconnect();
}
}
[TestMethod]
[TestCategory("Sftp")]
public void Test_Sftp_ListDirectory_Current()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
var files = sftp.ListDirectory(".");
Assert.IsTrue(files.Count() > 0);
foreach (var file in files)
{
Debug.WriteLine(file.FullName);
}
sftp.Disconnect();
}
}
#if NET6_0_OR_GREATER
[TestMethod]
[TestCategory("Sftp")]
public async Task Test_Sftp_ListDirectoryAsync_Current()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromMinutes(1));
var count = 0;
await foreach(var file in sftp.ListDirectoryAsync(".", cts.Token))
{
count++;
Debug.WriteLine(file.FullName);
}
Assert.IsTrue(count > 0);
sftp.Disconnect();
}
}
#endif
[TestMethod]
[TestCategory("Sftp")]
public void Test_Sftp_ListDirectory_Empty()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
var files = sftp.ListDirectory(string.Empty);
Assert.IsTrue(files.Count() > 0);
foreach (var file in files)
{
Debug.WriteLine(file.FullName);
}
sftp.Disconnect();
}
}
[TestMethod]
[TestCategory("Sftp")]
[Description("Test passing null to ListDirectory.")]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_Sftp_ListDirectory_Null()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
var files = sftp.ListDirectory(null);
Assert.IsTrue(files.Count() > 0);
foreach (var file in files)
{
Debug.WriteLine(file.FullName);
}
sftp.Disconnect();
}
}
[TestMethod]
[TestCategory("Sftp")]
public void Test_Sftp_ListDirectory_HugeDirectory()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
// Create 10000 directory items
for (int i = 0; i < 10000; i++)
{
sftp.CreateDirectory(string.Format("test_{0}", i));
}
var files = sftp.ListDirectory(".");
// Ensure that directory has at least 10000 items
Assert.IsTrue(files.Count() > 10000);
sftp.Disconnect();
}
RemoveAllFiles();
}
[TestMethod]
[TestCategory("Sftp")]
public void Test_Sftp_Change_Directory()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet");
sftp.CreateDirectory("test1");
sftp.ChangeDirectory("test1");
Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1");
sftp.CreateDirectory("test1_1");
sftp.CreateDirectory("test1_2");
sftp.CreateDirectory("test1_3");
var files = sftp.ListDirectory(".");
Assert.IsTrue(files.First().FullName.StartsWith(string.Format("{0}", sftp.WorkingDirectory)));
sftp.ChangeDirectory("test1_1");
Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_1");
sftp.ChangeDirectory("../test1_2");
Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_2");
sftp.ChangeDirectory("..");
Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1");
sftp.ChangeDirectory("..");
Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet");
files = sftp.ListDirectory("test1/test1_1");
Assert.IsTrue(files.First().FullName.StartsWith(string.Format("{0}/test1/test1_1", sftp.WorkingDirectory)));
sftp.ChangeDirectory("test1/test1_1");
Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_1");
sftp.ChangeDirectory("/home/sshnet/test1/test1_1");
Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_1");
sftp.ChangeDirectory("/home/sshnet/test1/test1_1/../test1_2");
Assert.AreEqual(sftp.WorkingDirectory, "/home/sshnet/test1/test1_2");
sftp.ChangeDirectory("../../");
sftp.DeleteDirectory("test1/test1_1");
sftp.DeleteDirectory("test1/test1_2");
sftp.DeleteDirectory("test1/test1_3");
sftp.DeleteDirectory("test1");
sftp.Disconnect();
}
RemoveAllFiles();
}
[TestMethod]
[TestCategory("Sftp")]
[Description("Test passing null to ChangeDirectory.")]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_Sftp_ChangeDirectory_Null()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.ChangeDirectory(null);
sftp.Disconnect();
}
}
[TestMethod]
[TestCategory("Sftp")]
[Description("Test calling EndListDirectory method more then once.")]
[ExpectedException(typeof(ArgumentException))]
public void Test_Sftp_Call_EndListDirectory_Twice()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
var ar = sftp.BeginListDirectory("/", null, null);
var result = sftp.EndListDirectory(ar);
var result1 = sftp.EndListDirectory(ar);
}
}
}
}
@@ -1,21 +1,15 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Renci.SshNet.Tests.Properties;
using System;
using System.IO;
namespace Renci.SshNet.Tests.Classes
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
/// <summary>
/// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
/// </summary>
public partial class SftpClientTest
public partial class SftpClientTest : IntegrationTestBase
{
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
public void Test_Sftp_Rename_File()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
@@ -23,7 +17,7 @@ namespace Renci.SshNet.Tests.Classes
string remoteFileName1 = Path.GetRandomFileName();
string remoteFileName2 = Path.GetRandomFileName();
this.CreateTestFile(uploadedFileName, 1);
CreateTestFile(uploadedFileName, 1);
using (var file = File.OpenRead(uploadedFileName))
{
@@ -42,12 +36,11 @@ namespace Renci.SshNet.Tests.Classes
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[Description("Test passing null to RenameFile.")]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_Sftp_RenameFile_Null()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
@@ -57,4 +50,4 @@ namespace Renci.SshNet.Tests.Classes
}
}
}
}
}
@@ -0,0 +1,56 @@
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
/// <summary>
/// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
/// </summary>
public partial class SftpClientTest : IntegrationTestBase
{
[TestMethod]
[TestCategory("Sftp")]
public async Task Test_Sftp_RenameFileAsync()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
await sftp.ConnectAsync(default);
string uploadedFileName = Path.GetTempFileName();
string remoteFileName1 = Path.GetRandomFileName();
string remoteFileName2 = Path.GetRandomFileName();
CreateTestFile(uploadedFileName, 1);
using (var file = File.OpenRead(uploadedFileName))
{
using (Stream remoteStream = await sftp.OpenAsync(remoteFileName1, FileMode.CreateNew, FileAccess.Write, default))
{
await file.CopyToAsync(remoteStream, 81920, default);
}
}
await sftp.RenameFileAsync(remoteFileName1, remoteFileName2, default);
File.Delete(uploadedFileName);
sftp.Disconnect();
}
RemoveAllFiles();
}
[TestMethod]
[TestCategory("Sftp")]
[Description("Test passing null to RenameFile.")]
[ExpectedException(typeof(ArgumentNullException))]
public async Task Test_Sftp_RenameFileAsync_Null()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
await sftp.ConnectAsync(default);
await sftp.RenameFileAsync(null, null, default);
sftp.Disconnect();
}
}
}
}
@@ -1,31 +1,26 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Renci.SshNet.Tests.Properties;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Diagnostics;
namespace Renci.SshNet.Tests.Classes
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
/// <summary>
/// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
/// </summary>
public partial class SftpClientTest
public partial class SftpClientTest : IntegrationTestBase
{
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
public void Test_Sftp_SynchronizeDirectories()
{
RemoveAllFiles();
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
string uploadedFileName = Path.GetTempFileName();
string sourceDir = Path.GetDirectoryName(uploadedFileName);
string destDir = "/";
string destDir = "/home/sshnet/";
string searchPattern = Path.GetFileName(uploadedFileName);
var upLoadedFiles = sftp.SynchronizeDirectories(sourceDir, destDir, searchPattern);
@@ -42,19 +37,18 @@ namespace Renci.SshNet.Tests.Classes
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
public void Test_Sftp_BeginSynchronizeDirectories()
{
RemoveAllFiles();
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
string uploadedFileName = Path.GetTempFileName();
string sourceDir = Path.GetDirectoryName(uploadedFileName);
string destDir = "/";
string destDir = "/home/sshnet/";
string searchPattern = Path.GetFileName(uploadedFileName);
var asyncResult = sftp.BeginSynchronizeDirectories(sourceDir,
@@ -83,4 +77,4 @@ namespace Renci.SshNet.Tests.Classes
}
}
}
}
}
@@ -1,34 +1,27 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Renci.SshNet.Common;
using Renci.SshNet.Common;
using Renci.SshNet.Sftp;
using Renci.SshNet.Tests.Properties;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
namespace Renci.SshNet.Tests.Classes
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
/// <summary>
/// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
/// </summary>
public partial class SftpClientTest
public partial class SftpClientTest : IntegrationTestBase
{
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
public void Test_Sftp_Upload_And_Download_1MB_File()
{
RemoveAllFiles();
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
string uploadedFileName = Path.GetTempFileName();
string remoteFileName = Path.GetRandomFileName();
var uploadedFileName = Path.GetTempFileName();
var remoteFileName = Path.GetRandomFileName();
this.CreateTestFile(uploadedFileName, 1);
CreateTestFile(uploadedFileName, 1);
// Calculate has value
var uploadedHash = CalculateMD5(uploadedFileName);
@@ -38,7 +31,7 @@ namespace Renci.SshNet.Tests.Classes
sftp.UploadFile(file, remoteFileName);
}
string downloadedFileName = Path.GetTempFileName();
var downloadedFileName = Path.GetTempFileName();
using (var file = File.OpenWrite(downloadedFileName))
{
@@ -60,18 +53,17 @@ namespace Renci.SshNet.Tests.Classes
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[ExpectedException(typeof(SftpPermissionDeniedException))]
public void Test_Sftp_Upload_Forbidden()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
string uploadedFileName = Path.GetTempFileName();
string remoteFileName = "/root/1";
var uploadedFileName = Path.GetTempFileName();
var remoteFileName = "/root/1";
this.CreateTestFile(uploadedFileName, 1);
CreateTestFile(uploadedFileName, 1);
using (var file = File.OpenRead(uploadedFileName))
{
@@ -84,7 +76,6 @@ namespace Renci.SshNet.Tests.Classes
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
public void Test_Sftp_Multiple_Async_Upload_And_Download_10Files_5MB_Each()
{
var maxFiles = 10;
@@ -92,20 +83,23 @@ namespace Renci.SshNet.Tests.Classes
RemoveAllFiles();
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.OperationTimeout = TimeSpan.FromMinutes(1);
sftp.Connect();
var testInfoList = new Dictionary<string, TestInfo>();
for (int i = 0; i < maxFiles; i++)
for (var i = 0; i < maxFiles; i++)
{
var testInfo = new TestInfo();
testInfo.UploadedFileName = Path.GetTempFileName();
testInfo.DownloadedFileName = Path.GetTempFileName();
testInfo.RemoteFileName = Path.GetRandomFileName();
var testInfo = new TestInfo
{
UploadedFileName = Path.GetTempFileName(),
DownloadedFileName = Path.GetTempFileName(),
RemoteFileName = Path.GetRandomFileName()
};
this.CreateTestFile(testInfo.UploadedFileName, maxSize);
CreateTestFile(testInfo.UploadedFileName, maxSize);
// Calculate hash value
testInfo.UploadedHash = CalculateMD5(testInfo.UploadedFileName);
@@ -130,7 +124,7 @@ namespace Renci.SshNet.Tests.Classes
}
// Wait for upload to finish
bool uploadCompleted = false;
var uploadCompleted = false;
while (!uploadCompleted)
{
// Assume upload completed
@@ -174,7 +168,7 @@ namespace Renci.SshNet.Tests.Classes
}
// Wait for download to finish
bool downloadCompleted = false;
var downloadCompleted = false;
while (!downloadCompleted)
{
// Assume download completed
@@ -206,6 +200,11 @@ namespace Renci.SshNet.Tests.Classes
testInfo.DownloadedHash = CalculateMD5(testInfo.DownloadedFileName);
Console.WriteLine(remoteFile);
Console.WriteLine("UploadedBytes: "+ testInfo.UploadResult.UploadedBytes);
Console.WriteLine("DownloadedBytes: " + testInfo.DownloadResult.DownloadedBytes);
Console.WriteLine("UploadedHash: " + testInfo.UploadedHash);
Console.WriteLine("DownloadedHash: " + testInfo.DownloadedHash);
if (!(testInfo.UploadResult.UploadedBytes > 0 && testInfo.DownloadResult.DownloadedBytes > 0 && testInfo.DownloadResult.DownloadedBytes == testInfo.UploadResult.UploadedBytes))
{
uploadDownloadSizeOk = false;
@@ -238,21 +237,20 @@ namespace Renci.SshNet.Tests.Classes
// TODO: Split this test into multiple tests
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[Description("Test that delegates passed to BeginUploadFile, BeginDownloadFile and BeginListDirectory are actually called.")]
public void Test_Sftp_Ensure_Async_Delegates_Called_For_BeginFileUpload_BeginFileDownload_BeginListDirectory()
{
RemoveAllFiles();
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
string remoteFileName = Path.GetRandomFileName();
string localFileName = Path.GetRandomFileName();
bool uploadDelegateCalled = false;
bool downloadDelegateCalled = false;
bool listDirectoryDelegateCalled = false;
var remoteFileName = Path.GetRandomFileName();
var localFileName = Path.GetRandomFileName();
var uploadDelegateCalled = false;
var downloadDelegateCalled = false;
var listDirectoryDelegateCalled = false;
IAsyncResult asyncResult;
// Test for BeginUploadFile.
@@ -261,11 +259,14 @@ namespace Renci.SshNet.Tests.Classes
using (var fileStream = File.OpenRead(localFileName))
{
asyncResult = sftp.BeginUploadFile(fileStream, remoteFileName, delegate(IAsyncResult ar)
{
sftp.EndUploadFile(ar);
uploadDelegateCalled = true;
}, null);
asyncResult = sftp.BeginUploadFile(fileStream,
remoteFileName,
delegate(IAsyncResult ar)
{
sftp.EndUploadFile(ar);
uploadDelegateCalled = true;
},
null);
while (!asyncResult.IsCompleted)
{
@@ -282,11 +283,14 @@ namespace Renci.SshNet.Tests.Classes
asyncResult = null;
using (var fileStream = File.OpenWrite(localFileName))
{
asyncResult = sftp.BeginDownloadFile(remoteFileName, fileStream, delegate(IAsyncResult ar)
{
sftp.EndDownloadFile(ar);
downloadDelegateCalled = true;
}, null);
asyncResult = sftp.BeginDownloadFile(remoteFileName,
fileStream,
delegate(IAsyncResult ar)
{
sftp.EndDownloadFile(ar);
downloadDelegateCalled = true;
},
null);
while (!asyncResult.IsCompleted)
{
@@ -301,11 +305,13 @@ namespace Renci.SshNet.Tests.Classes
// Test for BeginListDirectory.
asyncResult = null;
asyncResult = sftp.BeginListDirectory(sftp.WorkingDirectory, delegate(IAsyncResult ar)
{
sftp.EndListDirectory(ar);
listDirectoryDelegateCalled = true;
}, null);
asyncResult = sftp.BeginListDirectory(sftp.WorkingDirectory,
delegate(IAsyncResult ar)
{
_ = sftp.EndListDirectory(ar);
listDirectoryDelegateCalled = true;
},
null);
while (!asyncResult.IsCompleted)
{
@@ -318,64 +324,60 @@ namespace Renci.SshNet.Tests.Classes
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[Description("Test passing null to BeginUploadFile")]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_Sftp_BeginUploadFile_StreamIsNull()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.BeginUploadFile(null, "aaaaa", null, null);
_ = sftp.BeginUploadFile(null, "aaaaa", null, null);
sftp.Disconnect();
}
}
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[Description("Test passing null to BeginUploadFile")]
[ExpectedException(typeof(ArgumentException))]
public void Test_Sftp_BeginUploadFile_FileNameIsWhiteSpace()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.BeginUploadFile(new MemoryStream(), " ", null, null);
_ = sftp.BeginUploadFile(new MemoryStream(), " ", null, null);
sftp.Disconnect();
}
}
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[Description("Test passing null to BeginUploadFile")]
[ExpectedException(typeof(ArgumentException))]
public void Test_Sftp_BeginUploadFile_FileNameIsNull()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.BeginUploadFile(new MemoryStream(), null, null, null);
_ = sftp.BeginUploadFile(new MemoryStream(), null, null, null);
sftp.Disconnect();
}
}
[TestMethod]
[TestCategory("Sftp")]
[TestCategory("integration")]
[ExpectedException(typeof(ArgumentException))]
public void Test_Sftp_EndUploadFile_Invalid_Async_Handle()
{
using (var sftp = new SftpClient(Resources.HOST, Resources.USERNAME, Resources.PASSWORD))
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
var async1 = sftp.BeginListDirectory("/", null, null);
var filename = Path.GetTempFileName();
this.CreateTestFile(filename, 100);
CreateTestFile(filename, 100);
var async2 = sftp.BeginUploadFile(File.OpenRead(filename), "test", null, null);
sftp.EndUploadFile(async1);
}
}
}
}
}
@@ -0,0 +1,68 @@
using System.Security.Cryptography;
using Renci.SshNet.Sftp;
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
/// <summary>
/// Implementation of the SSH File Transfer Protocol (SFTP) over SSH.
/// </summary>
[TestClass]
public partial class SftpClientTest
{
protected static string CalculateMD5(string fileName)
{
using (FileStream file = new FileStream(fileName, FileMode.Open))
{
var hash = MD5.HashData(file);
file.Close();
StringBuilder sb = new StringBuilder();
for (var i = 0; i < hash.Length; i++)
{
sb.Append(hash[i].ToString("x2"));
}
return sb.ToString();
}
}
private void RemoveAllFiles()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
client.RunCommand("rm -rf *");
client.Disconnect();
}
}
/// <summary>
/// Helper class to help with upload and download testing
/// </summary>
private class TestInfo
{
public string RemoteFileName { get; set; }
public string UploadedFileName { get; set; }
public string DownloadedFileName { get; set; }
//public ulong UploadedBytes { get; set; }
//public ulong DownloadedBytes { get; set; }
public FileStream UploadedFile { get; set; }
public FileStream DownloadedFile { get; set; }
public string UploadedHash { get; set; }
public string DownloadedHash { get; set; }
public SftpUploadAsyncResult UploadResult { get; set; }
public SftpDownloadAsyncResult DownloadResult { get; set; }
}
}
}
@@ -0,0 +1,120 @@
using Renci.SshNet.Common;
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
/// <summary>
/// Represents SFTP file information
/// </summary>
[TestClass]
public class SftpFileTest : IntegrationTestBase
{
[TestMethod]
[TestCategory("Sftp")]
public void Test_Get_Root_Directory()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
var directory = sftp.Get("/");
Assert.AreEqual("/", directory.FullName);
Assert.IsTrue(directory.IsDirectory);
Assert.IsFalse(directory.IsRegularFile);
}
}
[TestMethod]
[TestCategory("Sftp")]
[ExpectedException(typeof(SftpPathNotFoundException))]
public void Test_Get_Invalid_Directory()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.Get("/xyz");
}
}
[TestMethod]
[TestCategory("Sftp")]
public void Test_Get_File()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.UploadFile(new MemoryStream(), "abc.txt");
var file = sftp.Get("abc.txt");
Assert.AreEqual("/home/sshnet/abc.txt", file.FullName);
Assert.IsTrue(file.IsRegularFile);
Assert.IsFalse(file.IsDirectory);
}
}
[TestMethod]
[TestCategory("Sftp")]
[Description("Test passing null to Get.")]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_Get_File_Null()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
var file = sftp.Get(null);
sftp.Disconnect();
}
}
[TestMethod]
[TestCategory("Sftp")]
public void Test_Get_International_File()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
sftp.UploadFile(new MemoryStream(), "test-üöä-");
var file = sftp.Get("test-üöä-");
Assert.AreEqual("/home/sshnet/test-üöä-", file.FullName);
Assert.IsTrue(file.IsRegularFile);
Assert.IsFalse(file.IsDirectory);
}
}
[TestMethod]
[TestCategory("Sftp")]
public void Test_Sftp_SftpFile_MoveTo()
{
using (var sftp = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
sftp.Connect();
string uploadedFileName = Path.GetTempFileName();
string remoteFileName = Path.GetRandomFileName();
string newFileName = Path.GetRandomFileName();
CreateTestFile(uploadedFileName, 1);
using (var file = File.OpenRead(uploadedFileName))
{
sftp.UploadFile(file, remoteFileName);
}
var sftpFile = sftp.Get(remoteFileName);
sftpFile.MoveTo(newFileName);
Assert.AreEqual(newFileName, sftpFile.Name);
sftp.Disconnect();
}
}
}
}
@@ -0,0 +1,545 @@
using System.Diagnostics;
using Renci.SshNet.Common;
namespace Renci.SshNet.IntegrationTests.OldIntegrationTests
{
/// <summary>
/// Represents SSH command that can be executed.
/// </summary>
[TestClass]
public class SshCommandTest : IntegrationTestBase
{
[TestMethod]
public void Test_Run_SingleCommand()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
#region Example SshCommand RunCommand Result
client.Connect();
var testValue = Guid.NewGuid().ToString();
var command = client.RunCommand(string.Format("echo {0}", testValue));
var result = command.Result;
result = result.Substring(0, result.Length - 1); // Remove \n character returned by command
client.Disconnect();
#endregion
Assert.IsTrue(result.Equals(testValue));
}
}
[TestMethod]
public void Test_Execute_SingleCommand()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
#region Example SshCommand CreateCommand Execute
client.Connect();
var testValue = Guid.NewGuid().ToString();
var command = string.Format("echo {0}", testValue);
var cmd = client.CreateCommand(command);
var result = cmd.Execute();
result = result.Substring(0, result.Length - 1); // Remove \n character returned by command
client.Disconnect();
#endregion
Assert.IsTrue(result.Equals(testValue));
}
}
[TestMethod]
public void Test_Execute_OutputStream()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
#region Example SshCommand CreateCommand Execute OutputStream
client.Connect();
var cmd = client.CreateCommand("ls -l"); // very long list
var asynch = cmd.BeginExecute();
var reader = new StreamReader(cmd.OutputStream);
while (!asynch.IsCompleted)
{
var result = reader.ReadToEnd();
if (string.IsNullOrEmpty(result))
{
continue;
}
Console.Write(result);
}
_ = cmd.EndExecute(asynch);
client.Disconnect();
#endregion
Assert.Inconclusive();
}
}
[TestMethod]
public void Test_Execute_ExtendedOutputStream()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
#region Example SshCommand CreateCommand Execute ExtendedOutputStream
client.Connect();
var cmd = client.CreateCommand("echo 12345; echo 654321 >&2");
var result = cmd.Execute();
Console.Write(result);
var reader = new StreamReader(cmd.ExtendedOutputStream);
Console.WriteLine("DEBUG:");
Console.Write(reader.ReadToEnd());
client.Disconnect();
#endregion
Assert.Inconclusive();
}
}
[TestMethod]
[ExpectedException(typeof(SshOperationTimeoutException))]
public void Test_Execute_Timeout()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
#region Example SshCommand CreateCommand Execute CommandTimeout
client.Connect();
var cmd = client.CreateCommand("sleep 10s");
cmd.CommandTimeout = TimeSpan.FromSeconds(5);
cmd.Execute();
client.Disconnect();
#endregion
}
}
[TestMethod]
public void Test_Execute_Infinite_Timeout()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
var cmd = client.CreateCommand("sleep 10s");
cmd.Execute();
client.Disconnect();
}
}
[TestMethod]
public void Test_Execute_InvalidCommand()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
var cmd = client.CreateCommand(";");
cmd.Execute();
if (string.IsNullOrEmpty(cmd.Error))
{
Assert.Fail("Operation should fail");
}
Assert.IsTrue(cmd.ExitStatus > 0);
client.Disconnect();
}
}
[TestMethod]
public void Test_Execute_InvalidCommand_Then_Execute_ValidCommand()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
var cmd = client.CreateCommand(";");
cmd.Execute();
if (string.IsNullOrEmpty(cmd.Error))
{
Assert.Fail("Operation should fail");
}
Assert.IsTrue(cmd.ExitStatus > 0);
var result = ExecuteTestCommand(client);
client.Disconnect();
Assert.IsTrue(result);
}
}
[TestMethod]
public void Test_Execute_Command_with_ExtendedOutput()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
var cmd = client.CreateCommand("echo 12345; echo 654321 >&2");
cmd.Execute();
//var extendedData = Encoding.ASCII.GetString(cmd.ExtendedOutputStream.ToArray());
var extendedData = new StreamReader(cmd.ExtendedOutputStream, Encoding.ASCII).ReadToEnd();
client.Disconnect();
Assert.AreEqual("12345\n", cmd.Result);
Assert.AreEqual("654321\n", extendedData);
}
}
[TestMethod]
public void Test_Execute_Command_Reconnect_Execute_Command()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
var result = ExecuteTestCommand(client);
Assert.IsTrue(result);
client.Disconnect();
client.Connect();
result = ExecuteTestCommand(client);
Assert.IsTrue(result);
client.Disconnect();
}
}
[TestMethod]
public void Test_Execute_Command_ExitStatus()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
#region Example SshCommand RunCommand ExitStatus
client.Connect();
var cmd = client.RunCommand("exit 128");
Console.WriteLine(cmd.ExitStatus);
client.Disconnect();
#endregion
Assert.IsTrue(cmd.ExitStatus == 128);
}
}
[TestMethod]
public void Test_Execute_Command_Asynchronously()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
var cmd = client.CreateCommand("sleep 5s; echo 'test'");
var asyncResult = cmd.BeginExecute(null, null);
while (!asyncResult.IsCompleted)
{
Thread.Sleep(100);
}
cmd.EndExecute(asyncResult);
Assert.IsTrue(cmd.Result == "test\n");
client.Disconnect();
}
}
[TestMethod]
public void Test_Execute_Command_Asynchronously_With_Error()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
var cmd = client.CreateCommand("sleep 5s; ;");
var asyncResult = cmd.BeginExecute(null, null);
while (!asyncResult.IsCompleted)
{
Thread.Sleep(100);
}
cmd.EndExecute(asyncResult);
Assert.IsFalse(string.IsNullOrEmpty(cmd.Error));
client.Disconnect();
}
}
[TestMethod]
public void Test_Execute_Command_Asynchronously_With_Callback()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
var callbackCalled = false;
var cmd = client.CreateCommand("sleep 5s; echo 'test'");
var asyncResult = cmd.BeginExecute(new AsyncCallback((s) =>
{
callbackCalled = true;
}), null);
while (!asyncResult.IsCompleted)
{
Thread.Sleep(100);
}
cmd.EndExecute(asyncResult);
Assert.IsTrue(callbackCalled);
client.Disconnect();
}
}
[TestMethod]
public void Test_Execute_Command_Asynchronously_With_Callback_On_Different_Thread()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
var currentThreadId = Thread.CurrentThread.ManagedThreadId;
int callbackThreadId = 0;
var cmd = client.CreateCommand("sleep 5s; echo 'test'");
var asyncResult = cmd.BeginExecute(new AsyncCallback((s) =>
{
callbackThreadId = Thread.CurrentThread.ManagedThreadId;
}), null);
while (!asyncResult.IsCompleted)
{
Thread.Sleep(100);
}
cmd.EndExecute(asyncResult);
Assert.AreNotEqual(currentThreadId, callbackThreadId);
client.Disconnect();
}
}
/// <summary>
/// Tests for Issue 563.
/// </summary>
[WorkItem(563), TestMethod]
public void Test_Execute_Command_Same_Object_Different_Commands()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
var cmd = client.CreateCommand("echo 12345");
cmd.Execute();
Assert.AreEqual("12345\n", cmd.Result);
cmd.Execute("echo 23456");
Assert.AreEqual("23456\n", cmd.Result);
client.Disconnect();
}
}
[TestMethod]
public void Test_Get_Result_Without_Execution()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
var cmd = client.CreateCommand("ls -l");
Assert.IsTrue(string.IsNullOrEmpty(cmd.Result));
client.Disconnect();
}
}
[TestMethod]
public void Test_Get_Error_Without_Execution()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
var cmd = client.CreateCommand("ls -l");
Assert.IsTrue(string.IsNullOrEmpty(cmd.Error));
client.Disconnect();
}
}
[WorkItem(703), TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void Test_EndExecute_Before_BeginExecute()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
var cmd = client.CreateCommand("ls -l");
cmd.EndExecute(null);
client.Disconnect();
}
}
/// <summary>
///A test for BeginExecute
///</summary>
[TestMethod()]
public void BeginExecuteTest()
{
string expected = "123\n";
string result;
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
#region Example SshCommand CreateCommand BeginExecute IsCompleted EndExecute
client.Connect();
var cmd = client.CreateCommand("sleep 15s;echo 123"); // Perform long running task
var asynch = cmd.BeginExecute();
while (!asynch.IsCompleted)
{
// Waiting for command to complete...
Thread.Sleep(2000);
}
result = cmd.EndExecute(asynch);
client.Disconnect();
#endregion
Assert.IsNotNull(asynch);
Assert.AreEqual(expected, result);
}
}
[TestMethod]
public void Test_Execute_Invalid_Command()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
#region Example SshCommand CreateCommand Error
client.Connect();
var cmd = client.CreateCommand(";");
cmd.Execute();
if (!string.IsNullOrEmpty(cmd.Error))
{
Console.WriteLine(cmd.Error);
}
client.Disconnect();
#endregion
Assert.Inconclusive();
}
}
[TestMethod]
public void Test_MultipleThread_Example_MultipleConnections()
{
try
{
#region Example SshCommand RunCommand Parallel
Parallel.For(0, 100,
() =>
{
var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password);
client.Connect();
return client;
},
(int counter, ParallelLoopState pls, SshClient client) =>
{
var result = client.RunCommand("echo 123");
Debug.WriteLine(string.Format("TestMultipleThreadMultipleConnections #{0}", counter));
return client;
},
(SshClient client) =>
{
client.Disconnect();
client.Dispose();
}
);
#endregion
}
catch (Exception exp)
{
Assert.Fail(exp.ToString());
}
}
[TestMethod]
public void Test_MultipleThread_100_MultipleConnections()
{
try
{
Parallel.For(0, 100,
() =>
{
var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password);
client.Connect();
return client;
},
(int counter, ParallelLoopState pls, SshClient client) =>
{
var result = ExecuteTestCommand(client);
Debug.WriteLine(string.Format("TestMultipleThreadMultipleConnections #{0}", counter));
Assert.IsTrue(result);
return client;
},
(SshClient client) =>
{
client.Disconnect();
client.Dispose();
}
);
}
catch (Exception exp)
{
Assert.Fail(exp.ToString());
}
}
[TestMethod]
public void Test_MultipleThread_100_MultipleSessions()
{
using (var client = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password))
{
client.Connect();
Parallel.For(0, 100,
(counter) =>
{
var result = ExecuteTestCommand(client);
Debug.WriteLine(string.Format("TestMultipleThreadMultipleConnections #{0}", counter));
Assert.IsTrue(result);
}
);
client.Disconnect();
}
}
private static bool ExecuteTestCommand(SshClient s)
{
var testValue = Guid.NewGuid().ToString();
var command = string.Format("echo {0}", testValue);
var cmd = s.CreateCommand(command);
var result = cmd.Execute();
result = result.Substring(0, result.Length - 1); // Remove \n character returned by command
return result.Equals(testValue);
}
}
}
@@ -0,0 +1,102 @@
using Renci.SshNet.IntegrationTests.Common;
using Renci.SshNet.TestTools.OpenSSH;
namespace Renci.SshNet.IntegrationTests
{
[TestClass]
public class PrivateKeyAuthenticationTests : TestBase
{
private IConnectionInfoFactory _connectionInfoFactory;
private RemoteSshdConfig _remoteSshdConfig;
[TestInitialize]
public void SetUp()
{
_connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort);
_remoteSshdConfig = new RemoteSshd(new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort)).OpenConfig();
}
[TestCleanup]
public void TearDown()
{
_remoteSshdConfig?.Reset();
}
[TestMethod]
public void SshDss()
{
DoTest(PublicKeyAlgorithm.SshDss, "id_dsa");
}
[TestMethod]
public void SshRsa()
{
DoTest(PublicKeyAlgorithm.SshRsa, "id_rsa");
}
[TestMethod]
public void SshRsaSha256()
{
DoTest(PublicKeyAlgorithm.RsaSha2256, "id_rsa");
}
[TestMethod]
public void SshRsaSha512()
{
DoTest(PublicKeyAlgorithm.RsaSha2512, "id_rsa");
}
[TestMethod]
public void Ecdsa256()
{
DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp256, "key_ecdsa_256_openssh");
}
[TestMethod]
public void Ecdsa384()
{
DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp384, "key_ecdsa_384_openssh");
}
[TestMethod]
public void Ecdsa521()
{
DoTest(PublicKeyAlgorithm.EcdsaSha2Nistp521, "key_ecdsa_521_openssh");
}
[TestMethod]
public void Ed25519()
{
DoTest(PublicKeyAlgorithm.SshEd25519, "key_ed25519_openssh");
}
private void DoTest(PublicKeyAlgorithm publicKeyAlgorithm, string keyResource)
{
_remoteSshdConfig.ClearPublicKeyAcceptedAlgorithms()
.AddPublicKeyAcceptedAlgorithm(publicKeyAlgorithm)
.Update()
.Restart();
var connectionInfo = _connectionInfoFactory.Create(CreatePrivateKeyAuthenticationMethod(keyResource));
using (var client = new SshClient(connectionInfo))
{
client.Connect();
}
}
private PrivateKeyAuthenticationMethod CreatePrivateKeyAuthenticationMethod(string keyResource)
{
var privateKey = CreatePrivateKeyFromManifestResource("Renci.SshNet.IntegrationTests.resources.client." + keyResource);
return new PrivateKeyAuthenticationMethod(Users.Regular.UserName, privateKey);
}
private PrivateKeyFile CreatePrivateKeyFromManifestResource(string resourceName)
{
using (var stream = GetManifestResourceStream(resourceName))
{
return new PrivateKeyFile(stream);
}
}
}
}
@@ -0,0 +1,11 @@
namespace Renci.SshNet.IntegrationTests
{
class Program
{
#if NETFRAMEWORK
private static void Main()
{
}
#endif
}
}
@@ -0,0 +1,258 @@
using Renci.SshNet.TestTools.OpenSSH;
namespace Renci.SshNet.IntegrationTests
{
internal class RemoteSshd
{
private readonly IConnectionInfoFactory _connectionInfoFactory;
public RemoteSshd(IConnectionInfoFactory connectionInfoFactory)
{
_connectionInfoFactory = connectionInfoFactory;
}
public RemoteSshdConfig OpenConfig()
{
return new RemoteSshdConfig(this, _connectionInfoFactory);
}
public RemoteSshd Restart()
{
// Restart SSH daemon
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
// Kill all processes that start with 'sshd' and that run as root
var stopCommand = client.CreateCommand("sudo pkill -9 -U 0 sshd.pam");
var stopOutput = stopCommand.Execute();
if (stopCommand.ExitStatus != 0)
{
throw new ApplicationException($"Stopping ssh service failed with exit code {stopCommand.ExitStatus}.\r\n{stopOutput}\r\n{stopCommand.Error}");
}
var resetFailedCommand = client.CreateCommand("sudo /usr/sbin/sshd.pam");
var resetFailedOutput = resetFailedCommand.Execute();
if (resetFailedCommand.ExitStatus != 0)
{
throw new ApplicationException($"Reset failures for ssh service failed with exit code {resetFailedCommand.ExitStatus}.\r\n{resetFailedOutput}\r\n{resetFailedCommand.Error}");
}
}
return this;
}
}
internal class RemoteSshdConfig
{
private const string SshdConfigFilePath = "/etc/ssh/sshd_config";
private static readonly Encoding Utf8NoBom = new UTF8Encoding(false, true);
private readonly RemoteSshd _remoteSshd;
private readonly IConnectionInfoFactory _connectionInfoFactory;
private readonly SshdConfig _config;
public RemoteSshdConfig(RemoteSshd remoteSshd, IConnectionInfoFactory connectionInfoFactory)
{
_remoteSshd = remoteSshd;
_connectionInfoFactory = connectionInfoFactory;
using (var client = new ScpClient(_connectionInfoFactory.Create()))
{
client.Connect();
using (var memoryStream = new MemoryStream())
{
client.Download(SshdConfigFilePath, memoryStream);
memoryStream.Position = 0;
_config = SshdConfig.LoadFrom(memoryStream, Encoding.UTF8);
}
}
}
/// <summary>
/// Specifies whether challenge-response authentication is allowed.
/// </summary>
/// <param name="value"><see langword="true"/> to allow challenge-response authentication.</param>
/// <returns>
/// The current <see cref="RemoteSshdConfig"/> instance.
/// </returns>
public RemoteSshdConfig WithChallengeResponseAuthentication(bool? value)
{
_config.ChallengeResponseAuthentication = value;
return this;
}
/// <summary>
/// Specifies whether to allow keyboard-interactive authentication.
/// </summary>
/// <param name="value"><see langword="true"/> to allow keyboard-interactive authentication.</param>
/// <returns>
/// The current <see cref="RemoteSshdConfig"/> instance.
/// </returns>
public RemoteSshdConfig WithKeyboardInteractiveAuthentication(bool value)
{
_config.KeyboardInteractiveAuthentication = value;
return this;
}
/// <summary>
/// Specifies whether <c>sshd</c> should print /etc/motd when a user logs in interactively.
/// </summary>
/// <param name="value"><see langword="true"/> if <c>sshd</c> should print /etc/motd when a user logs in interactively.</param>
/// <returns>
/// The current <see cref="RemoteSshdConfig"/> instance.
/// </returns>
public RemoteSshdConfig PrintMotd(bool? value = true)
{
_config.PrintMotd = value;
return this;
}
/// <summary>
/// Specifies whether TCP forwarding is permitted.
/// </summary>
/// <param name="value"><see langword="true"/> to allow TCP forwarding.</param>
/// <returns>
/// The current <see cref="RemoteSshdConfig"/> instance.
/// </returns>
public RemoteSshdConfig AllowTcpForwarding(bool? value = true)
{
_config.AllowTcpForwarding = value;
return this;
}
public RemoteSshdConfig WithAuthenticationMethods(string user, string authenticationMethods)
{
var sshNetMatch = _config.Matches.FirstOrDefault(m => m.Users.Contains(user));
if (sshNetMatch == null)
{
sshNetMatch = new Match(new[] { user }, new string[0]);
_config.Matches.Add(sshNetMatch);
}
sshNetMatch.AuthenticationMethods = authenticationMethods;
return this;
}
public RemoteSshdConfig ClearCiphers()
{
_config.Ciphers.Clear();
return this;
}
public RemoteSshdConfig AddCipher(Cipher cipher)
{
_config.Ciphers.Add(cipher);
return this;
}
public RemoteSshdConfig ClearKeyExchangeAlgorithms()
{
_config.KeyExchangeAlgorithms.Clear();
return this;
}
public RemoteSshdConfig AddKeyExchangeAlgorithm(KeyExchangeAlgorithm keyExchangeAlgorithm)
{
_config.KeyExchangeAlgorithms.Add(keyExchangeAlgorithm);
return this;
}
public RemoteSshdConfig ClearPublicKeyAcceptedAlgorithms()
{
_config.PublicKeyAcceptedAlgorithms.Clear();
return this;
}
public RemoteSshdConfig AddPublicKeyAcceptedAlgorithm(PublicKeyAlgorithm publicKeyAlgorithm)
{
_config.PublicKeyAcceptedAlgorithms.Add(publicKeyAlgorithm);
return this;
}
public RemoteSshdConfig ClearMessageAuthenticationCodeAlgorithms()
{
_config.MessageAuthenticationCodeAlgorithms.Clear();
return this;
}
public RemoteSshdConfig AddMessageAuthenticationCodeAlgorithm(MessageAuthenticationCodeAlgorithm messageAuthenticationCodeAlgorithm)
{
_config.MessageAuthenticationCodeAlgorithms.Add(messageAuthenticationCodeAlgorithm);
return this;
}
public RemoteSshdConfig ClearHostKeyAlgorithms()
{
_config.HostKeyAlgorithms.Clear();
return this;
}
public RemoteSshdConfig AddHostKeyAlgorithm(HostKeyAlgorithm hostKeyAlgorithm)
{
_config.HostKeyAlgorithms.Add(hostKeyAlgorithm);
return this;
}
public RemoteSshdConfig ClearSubsystems()
{
_config.Subsystems.Clear();
return this;
}
public RemoteSshdConfig AddSubsystem(Subsystem subsystem)
{
_config.Subsystems.Add(subsystem);
return this;
}
public RemoteSshdConfig WithLogLevel(LogLevel logLevel)
{
_config.LogLevel = logLevel;
return this;
}
public RemoteSshdConfig WithUsePAM(bool usePAM)
{
_config.UsePAM = usePAM;
return this;
}
public RemoteSshdConfig ClearHostKeyFiles()
{
_config.HostKeyFiles.Clear();
return this;
}
public RemoteSshdConfig AddHostKeyFile(string hostKeyFile)
{
_config.HostKeyFiles.Add(hostKeyFile);
return this;
}
public RemoteSshd Update()
{
using (var client = new ScpClient(_connectionInfoFactory.Create()))
{
client.Connect();
using (var memoryStream = new MemoryStream())
using (var sw = new StreamWriter(memoryStream, Utf8NoBom))
{
sw.NewLine = "\n";
_config.SaveTo(sw);
sw.Flush();
memoryStream.Position = 0;
client.Upload(memoryStream, SshdConfigFilePath);
}
}
return _remoteSshd;
}
}
}
@@ -0,0 +1,66 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<!--
Even though we're not interested in producing XML docs for test projects, we have to enable this in order to have the .NET Compiler
Platform analyzers produce the IDE0005 (Remove unnecessary import) diagnostic.
To avoid warnings for missing XML docs, we add CS1591 (Missing XML comment for publicly visible type or member) to the NoWarn property.
We can stop producing XML docs for test projects (and remove the NoWarn for CS1591) once the following issue is fixed:
https://github.com/dotnet/roslyn/issues/41640.
-->
<NoWarn>$(NoWarn);CS1591;SYSLIB0021;SYSLIB1045;SYSLIB0014;IDE0220;IDE0010</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net7.0' ">
<DefineConstants>TRACE;FEATURE_MSTEST_DATATEST;FEATURE_SOCKET_EAP;FEATURE_ENCODING_ASCII;FEATURE_THREAD_SLEEP;FEATURE_THREAD_THREADPOOL</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="Testcontainers" Version="3.4.0" />
<PackageReference Include="System.Management" Version="4.7.0" />
<!--
Testcontainers has a dependency on SSH.NET which causes build warnings during assembly resolution:
warning MSB3243: No way to resolve conflict between "Renci.SshNet, Version=2023.0.0.0, Culture=neutral
, PublicKeyToken=1cee9f8bde3db106" and "Renci.SshNet, Version=2020.0.2.0, Culture=neutral, PublicKeyToken=1cee9f8bde3db
106". Choosing "Renci.SshNet, Version=2023.0.0.0, Culture=neutral, PublicKeyToken=1cee9f8bde3db106" arbitrarily.
To fix, we explicitly exclude the SSH.NET nuget package from this project's dependencies.
-->
<PackageReference Include="SSH.NET" Version="2020.0.2" ExcludeAssets="All" />
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Renci.SshNet.TestTools.OpenSSH\Renci.SshNet.TestTools.OpenSSH.csproj" />
<ProjectReference Include="..\Renci.SshNet\Renci.SshNet.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="resources\client\id_dsa" />
<EmbeddedResource Include="resources\client\id_dsa.ppk" />
<EmbeddedResource Include="resources\client\id_noaccess.rsa" />
<EmbeddedResource Include="resources\client\id_rsa" />
<EmbeddedResource Include="resources\client\id_rsa_with_pass" />
<EmbeddedResource Include="resources\client\key_ecdsa_256_openssh" />
<EmbeddedResource Include="resources\client\key_ecdsa_384_openssh" />
<EmbeddedResource Include="resources\client\key_ecdsa_521_openssh" />
<EmbeddedResource Include="resources\client\key_ed25519_openssh" />
<EmbeddedResource Include="resources\issue #70.png" />
</ItemGroup>
</Project>
@@ -0,0 +1,41 @@
namespace Renci.SshNet.IntegrationTests
{
/// <summary>
/// The SCP client integration tests
/// </summary>
[TestClass]
public class ScpClientTests : IntegrationTestBase, IDisposable
{
private readonly ScpClient _scpClient;
public ScpClientTests()
{
_scpClient = new ScpClient(SshServerHostName, SshServerPort, User.UserName, User.Password);
_scpClient.Connect();
}
[TestMethod]
public void Upload_And_Download_FileStream()
{
var file = $"/tmp/{Guid.NewGuid()}.txt";
var fileContent = "File content !@#$%^&*()_+{}:,./<>[];'\\|";
using var uploadStream = new MemoryStream(Encoding.UTF8.GetBytes(fileContent));
_scpClient.Upload(uploadStream, file);
using var downloadStream = new MemoryStream();
_scpClient.Download(file, downloadStream);
var result = Encoding.UTF8.GetString(downloadStream.ToArray());
Assert.AreEqual(fileContent, result);
}
public void Dispose()
{
_scpClient.Disconnect();
_scpClient.Dispose();
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,102 @@
using Renci.SshNet.Common;
namespace Renci.SshNet.IntegrationTests
{
/// <summary>
/// The SFTP client integration tests
/// </summary>
[TestClass]
public class SftpClientTests : IntegrationTestBase, IDisposable
{
private readonly SftpClient _sftpClient;
public SftpClientTests()
{
_sftpClient = new SftpClient(SshServerHostName, SshServerPort, User.UserName, User.Password);
_sftpClient.Connect();
}
[TestMethod]
public void Create_directory_with_contents_and_list_it()
{
var testDirectory = "/home/sshnet/sshnet-test";
var testFileName = "test-file.txt";
var testFilePath = $"{testDirectory}/{testFileName}";
var testContent = "file content";
// Create new directory and check if it exists
_sftpClient.CreateDirectory(testDirectory);
Assert.IsTrue(_sftpClient.Exists(testDirectory));
// Upload file and check if it exists
using var fileStream = new MemoryStream(Encoding.UTF8.GetBytes(testContent));
_sftpClient.UploadFile(fileStream, testFilePath);
Assert.IsTrue(_sftpClient.Exists(testFilePath));
// Check if ListDirectory works
var files = _sftpClient.ListDirectory(testDirectory);
_sftpClient.DeleteFile(testFilePath);
_sftpClient.DeleteDirectory(testDirectory);
var builder = new StringBuilder();
foreach (var file in files)
{
builder.AppendLine($"{file.FullName} {file.IsRegularFile} {file.IsDirectory}");
}
Assert.AreEqual(@"/home/sshnet/sshnet-test/. False True
/home/sshnet/sshnet-test/.. False True
/home/sshnet/sshnet-test/test-file.txt True False
", builder.ToString());
}
[TestMethod]
public async Task Create_directory_with_contents_and_list_it_async()
{
var testDirectory = "/home/sshnet/sshnet-test";
var testFileName = "test-file.txt";
var testFilePath = $"{testDirectory}/{testFileName}";
var testContent = "file content";
// Create new directory and check if it exists
_sftpClient.CreateDirectory(testDirectory);
Assert.IsTrue(_sftpClient.Exists(testDirectory));
// Upload file and check if it exists
using var fileStream = new MemoryStream(Encoding.UTF8.GetBytes(testContent));
_sftpClient.UploadFile(fileStream, testFilePath);
Assert.IsTrue(_sftpClient.Exists(testFilePath));
// Check if ListDirectory works
var files = _sftpClient.ListDirectoryAsync(testDirectory, CancellationToken.None);
var builder = new StringBuilder();
await foreach (var file in files)
{
builder.AppendLine($"{file.FullName} {file.IsRegularFile} {file.IsDirectory}");
}
_sftpClient.DeleteFile(testFilePath);
_sftpClient.DeleteDirectory(testDirectory);
Assert.AreEqual(@"/home/sshnet/sshnet-test/. False True
/home/sshnet/sshnet-test/.. False True
/home/sshnet/sshnet-test/test-file.txt True False
", builder.ToString());
}
[TestMethod]
[ExpectedException(typeof(SftpPermissionDeniedException), "Permission denied")]
public void Test_Sftp_ListDirectory_Permission_Denied()
{
_sftpClient.ListDirectory("/root");
}
public void Dispose()
{
_sftpClient.Disconnect();
_sftpClient.Dispose();
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,32 @@
namespace Renci.SshNet.IntegrationTests
{
/// <summary>
/// The SSH client integration tests
/// </summary>
[TestClass]
public class SshClientTests : IntegrationTestBase, IDisposable
{
private readonly SshClient _sshClient;
public SshClientTests()
{
_sshClient = new SshClient(SshServerHostName, SshServerPort, User.UserName, User.Password);
_sshClient.Connect();
}
[TestMethod]
public void Echo_Command_with_all_characters()
{
var builder = new StringBuilder();
var response = _sshClient.RunCommand("echo $'test !@#$%^&*()_+{}:,./<>[];\\|'");
Assert.AreEqual("test !@#$%^&*()_+{}:,./<>[];\\|\n", response.Result);
}
public void Dispose()
{
_sshClient.Disconnect();
_sshClient.Dispose();
}
}
}
@@ -0,0 +1,41 @@
namespace Renci.SshNet.IntegrationTests
{
internal class SshConnectionDisruptor
{
private readonly IConnectionInfoFactory _connectionInfoFactory;
public SshConnectionDisruptor(IConnectionInfoFactory connectionInfoFactory)
{
_connectionInfoFactory = connectionInfoFactory;
}
public SshConnectionRestorer BreakConnections()
{
var client = new SshClient(_connectionInfoFactory.Create());
client.Connect();
PauseSshd(client);
return new SshConnectionRestorer(client);
}
private static void PauseSshd(SshClient client)
{
var command = client.CreateCommand("sudo echo 'DenyUsers sshnet' >> /etc/ssh/sshd_config");
var output = command.Execute();
if (command.ExitStatus != 0)
{
throw new ApplicationException(
$"Blocking user sshnet failed with exit code {command.ExitStatus}.\r\n{output}\r\n{command.Error}");
}
command = client.CreateCommand("sudo pkill -9 -U sshnet -f sshd.pam");
output = command.Execute();
if (command.ExitStatus != 0)
{
throw new ApplicationException(
$"Killing sshd.pam service failed with exit code {command.ExitStatus}.\r\n{output}\r\n{command.Error}");
}
}
}
}
@@ -0,0 +1,36 @@
namespace Renci.SshNet.IntegrationTests
{
internal class SshConnectionRestorer : IDisposable
{
private SshClient _sshClient;
public SshConnectionRestorer(SshClient sshClient)
{
_sshClient = sshClient;
}
public void RestoreConnections()
{
var command = _sshClient.CreateCommand("sudo sed -i '/DenyUsers sshnet/d' /etc/ssh/sshd_config");
var output = command.Execute();
if (command.ExitStatus != 0)
{
throw new ApplicationException(
$"Unblocking user sshnet failed with exit code {command.ExitStatus}.\r\n{output}\r\n{command.Error}");
}
command = _sshClient.CreateCommand("sudo /usr/sbin/sshd.pam");
output = command.Execute();
if (command.ExitStatus != 0)
{
throw new ApplicationException(
$"Resuming ssh service failed with exit code {command.ExitStatus}.\r\n{output}\r\n{command.Error}");
}
}
public void Dispose()
{
_sshClient?.Dispose();
_sshClient = null;
}
}
}
@@ -0,0 +1,972 @@
using System.ComponentModel;
using System.Net;
using System.Net.Sockets;
using Renci.SshNet.Common;
using Renci.SshNet.IntegrationTests.Common;
namespace Renci.SshNet.IntegrationTests
{
[TestClass]
public class SshTests : TestBase
{
private IConnectionInfoFactory _connectionInfoFactory;
private IConnectionInfoFactory _adminConnectionInfoFactory;
private RemoteSshdConfig _remoteSshdConfig;
[TestInitialize]
public void SetUp()
{
_connectionInfoFactory = new LinuxVMConnectionFactory(SshServerHostName, SshServerPort);
_adminConnectionInfoFactory = new LinuxAdminConnectionFactory(SshServerHostName, SshServerPort);
_remoteSshdConfig = new RemoteSshd(_adminConnectionInfoFactory).OpenConfig();
_remoteSshdConfig.AllowTcpForwarding()
.PrintMotd(false)
.Update()
.Restart();
}
[TestCleanup]
public void TearDown()
{
_remoteSshdConfig?.Reset();
}
/// <summary>
/// Test for a channel that is being closed by the server.
/// </summary>
[TestMethod]
public void Ssh_ShellStream_Exit()
{
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
var terminalModes = new Dictionary<TerminalModes, uint>
{
{ TerminalModes.ECHO, 0 }
};
using (var shellStream = client.CreateShellStream("xterm", 80, 24, 800, 600, 1024, terminalModes))
{
shellStream.WriteLine("echo Hello!");
shellStream.WriteLine("exit");
Thread.Sleep(1000);
try
{
shellStream.Write("ABC");
Assert.Fail();
}
catch (ObjectDisposedException ex)
{
Assert.IsNull(ex.InnerException);
Assert.AreEqual("ShellStream", ex.ObjectName);
Assert.AreEqual($"Cannot access a disposed object.{Environment.NewLine}Object name: '{ex.ObjectName}'.", ex.Message);
}
var line = shellStream.ReadLine();
Assert.IsNotNull(line);
Assert.IsTrue(line.EndsWith("Hello!"), line);
// TODO: ReadLine should return null when the buffer is empty and the channel has been closed (issue #672)
try
{
line = shellStream.ReadLine();
Assert.Fail(line);
}
catch (NullReferenceException)
{
}
}
}
}
/// <summary>
/// https://github.com/sshnet/SSH.NET/issues/63
/// </summary>
[TestMethod]
[Category("Reproduction Tests")]
[Ignore]
public void Ssh_ShellStream_IntermittendOutput()
{
const string remoteFile = "/home/sshnet/test.sh";
var expectedResult = string.Join("\n",
"Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Line 5 ",
"Line 6");
var scriptBuilder = new StringBuilder();
scriptBuilder.Append("#!/bin/sh\n");
scriptBuilder.Append("echo Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
scriptBuilder.Append("sleep .5\n");
scriptBuilder.Append("echo Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
scriptBuilder.Append("echo Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
scriptBuilder.Append("echo Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
scriptBuilder.Append("sleep 2\n");
scriptBuilder.Append("echo \"Line 5 \"\n");
scriptBuilder.Append("echo Line 6 \n");
scriptBuilder.Append("exit 13\n");
using (var sshClient = new SshClient(_connectionInfoFactory.Create()))
{
sshClient.Connect();
CreateShellScript(_connectionInfoFactory, remoteFile, scriptBuilder.ToString());
try
{
var terminalModes = new Dictionary<TerminalModes, uint>
{
{ TerminalModes.ECHO, 0 }
};
using (var shellStream = sshClient.CreateShellStream("xterm", 80, 24, 800, 600, 1024, terminalModes))
{
shellStream.WriteLine(remoteFile);
Thread.Sleep(1200);
using (var reader = new StreamReader(shellStream, new UTF8Encoding(false), false, 10))
{
var lines = new List<string>();
string line = null;
while ((line = reader.ReadLine()) != null)
{
lines.Add(line);
}
Assert.AreEqual(6, lines.Count, string.Join("\n", lines));
Assert.AreEqual(expectedResult, string.Join("\n", lines));
}
}
}
finally
{
RemoveFileOrDirectory(sshClient, remoteFile);
}
}
}
/// <summary>
/// Issue 1555
/// </summary>
[TestMethod]
public void Ssh_CreateShell()
{
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
using (var input = new MemoryStream())
using (var output = new MemoryStream())
using (var extOutput = new MemoryStream())
{
var shell = client.CreateShell(input, output, extOutput);
shell.Start();
var inputWriter = new StreamWriter(input, Encoding.ASCII, 1024);
inputWriter.WriteLine("echo $PATH");
var outputReader = new StreamReader(output, Encoding.ASCII, false, 1024);
Console.WriteLine(outputReader.ReadToEnd());
shell.Stop();
}
}
}
[TestMethod]
public void Ssh_Command_IntermittendOutput_EndExecute()
{
const string remoteFile = "/home/sshnet/test.sh";
var expectedResult = string.Join("\n",
"Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Line 5 ",
"Line 6",
"");
var scriptBuilder = new StringBuilder();
scriptBuilder.Append("#!/bin/sh\n");
scriptBuilder.Append("echo Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
scriptBuilder.Append("sleep .5\n");
scriptBuilder.Append("echo Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
scriptBuilder.Append("echo Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
scriptBuilder.Append("echo Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
scriptBuilder.Append("sleep 2\n");
scriptBuilder.Append("echo \"Line 5 \"\n");
scriptBuilder.Append("echo Line 6 \n");
scriptBuilder.Append("exit 13\n");
using (var sshClient = new SshClient(_connectionInfoFactory.Create()))
{
sshClient.Connect();
CreateShellScript(_connectionInfoFactory, remoteFile, scriptBuilder.ToString());
try
{
using (var cmd = sshClient.CreateCommand("chmod 777 " + remoteFile))
{
cmd.Execute();
Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
}
using (var command = sshClient.CreateCommand(remoteFile))
{
var asyncResult = command.BeginExecute();
var actualResult = command.EndExecute(asyncResult);
Assert.AreEqual(expectedResult, actualResult);
Assert.AreEqual(expectedResult, command.Result);
Assert.AreEqual(13, command.ExitStatus);
}
}
finally
{
RemoveFileOrDirectory(sshClient, remoteFile);
}
}
}
/// <summary>
/// Ignored for now, because:
/// * OutputStream.Read(...) does not block when no data is available
/// * SshCommand.(Begin)Execute consumes *OutputStream*, advancing its position.
///
/// https://github.com/sshnet/SSH.NET/issues/650
/// </summary>
[TestMethod]
[Ignore]
public void Ssh_Command_IntermittendOutput_OutputStream()
{
const string remoteFile = "/home/sshnet/test.sh";
var expectedResult = string.Join("\n",
"Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Line 5 ",
"Line 6");
var scriptBuilder = new StringBuilder();
scriptBuilder.Append("#!/bin/sh\n");
scriptBuilder.Append("echo Line 1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
scriptBuilder.Append("sleep .5\n");
scriptBuilder.Append("echo Line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
scriptBuilder.Append("echo Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
scriptBuilder.Append("echo Line 4 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
scriptBuilder.Append("sleep 2\n");
scriptBuilder.Append("echo \"Line 5 \"\n");
scriptBuilder.Append("echo Line 6 \n");
scriptBuilder.Append("exit 13\n");
using (var sshClient = new SshClient(_connectionInfoFactory.Create()))
{
sshClient.Connect();
CreateShellScript(_connectionInfoFactory, remoteFile, scriptBuilder.ToString());
try
{
using (var cmd = sshClient.CreateCommand("chmod 777 " + remoteFile))
{
cmd.Execute();
Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
}
using (var command = sshClient.CreateCommand(remoteFile))
{
var asyncResult = command.BeginExecute();
using (var reader = new StreamReader(command.OutputStream, new UTF8Encoding(false), false, 10))
{
var lines = new List<string>();
string line = null;
while ((line = reader.ReadLine()) != null)
{
lines.Add(line);
}
Assert.AreEqual(6, lines.Count, string.Join("\n", lines));
Assert.AreEqual(expectedResult, string.Join("\n", lines));
Assert.AreEqual(13, command.ExitStatus);
}
var actualResult = command.EndExecute(asyncResult);
Assert.AreEqual(expectedResult, actualResult);
Assert.AreEqual(expectedResult, command.Result);
}
}
finally
{
RemoveFileOrDirectory(sshClient, remoteFile);
}
}
}
[TestMethod]
public void Ssh_DynamicPortForwarding_DisposeSshClientWithoutStoppingPort()
{
const string searchText = "HTTP/1.1 301 Moved Permanently";
const string hostName = "github.com";
var httpGetRequest = Encoding.ASCII.GetBytes($"GET / HTTP/1.1\r\nHost: {hostName}\r\n\r\n");
Socket socksSocket;
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(200);
client.Connect();
var forwardedPort = new ForwardedPortDynamic(1080);
forwardedPort.Exception += (sender, args) => Console.WriteLine(args.Exception.ToString());
client.AddForwardedPort(forwardedPort);
forwardedPort.Start();
var socksClient = new Socks5Handler(new IPEndPoint(IPAddress.Loopback, 1080),
string.Empty,
string.Empty);
socksSocket = socksClient.Connect(hostName, 80);
socksSocket.Send(httpGetRequest);
var httpResponse = GetHttpResponse(socksSocket, Encoding.ASCII);
Assert.IsTrue(httpResponse.Contains(searchText), httpResponse);
}
Assert.IsTrue(socksSocket.Connected);
// check if client socket was properly closed
Assert.AreEqual(0, socksSocket.Receive(new byte[1], 0, 1, SocketFlags.None));
}
[TestMethod]
public void Ssh_DynamicPortForwarding_DomainName()
{
const string searchText = "HTTP/1.1 301 Moved Permanently";
const string hostName = "github.com";
// Set-up a host alias for google.be on the remote server that is not known locally; this allows us to
// verify whether the host name is resolved remotely.
const string hostNameAlias = "dynamicportforwarding-test.for.sshnet";
// Construct a HTTP request for which we expected the response to contain the search text.
var httpGetRequest = Encoding.ASCII.GetBytes($"GET / HTTP/1.1\r\nHost: {hostName}\r\n\r\n");
var ipAddresses = Dns.GetHostAddresses(hostName);
var hostsFileUpdated = AddOrUpdateHostsEntry(_adminConnectionInfoFactory, ipAddresses[0], hostNameAlias);
try
{
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(200);
client.Connect();
var forwardedPort = new ForwardedPortDynamic(1080);
forwardedPort.Exception += (sender, args) => Console.WriteLine(args.Exception.ToString());
client.AddForwardedPort(forwardedPort);
forwardedPort.Start();
var socksClient = new Socks5Handler(new IPEndPoint(IPAddress.Loopback, 1080),
string.Empty,
string.Empty);
var socksSocket = socksClient.Connect(hostNameAlias, 80);
socksSocket.Send(httpGetRequest);
var httpResponse = GetHttpResponse(socksSocket, Encoding.ASCII);
Assert.IsTrue(httpResponse.Contains(searchText), httpResponse);
// Verify if port is still open
socksSocket.Send(httpGetRequest);
GetHttpResponse(socksSocket, Encoding.ASCII);
forwardedPort.Stop();
Assert.IsTrue(socksSocket.Connected);
// check if client socket was properly closed
Assert.AreEqual(0, socksSocket.Receive(new byte[1], 0, 1, SocketFlags.None));
forwardedPort.Start();
// create new SOCKS connection and very whether the forwarded port is functional again
socksSocket = socksClient.Connect(hostNameAlias, 80);
socksSocket.Send(httpGetRequest);
httpResponse = GetHttpResponse(socksSocket, Encoding.ASCII);
Assert.IsTrue(httpResponse.Contains(searchText), httpResponse);
forwardedPort.Dispose();
Assert.IsTrue(socksSocket.Connected);
// check if client socket was properly closed
Assert.AreEqual(0, socksSocket.Receive(new byte[1], 0, 1, SocketFlags.None));
forwardedPort.Dispose();
}
}
finally
{
if (hostsFileUpdated)
{
RemoveHostsEntry(_adminConnectionInfoFactory, ipAddresses[0], hostNameAlias);
}
}
}
[TestMethod]
public void Ssh_DynamicPortForwarding_IPv4()
{
const string searchText = "HTTP/1.1 301 Moved Permanently";
const string hostName = "github.com";
var httpGetRequest = Encoding.ASCII.GetBytes($"GET /null HTTP/1.1\r\nHost: {hostName}\r\n\r\n");
var ipv4 = Dns.GetHostAddresses(hostName).FirstOrDefault(p => p.AddressFamily == AddressFamily.InterNetwork);
Assert.IsNotNull(ipv4, $@"No IPv4 address found for '{hostName}'.");
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.ConnectionInfo.Timeout = TimeSpan.FromSeconds(200);
client.Connect();
var forwardedPort = new ForwardedPortDynamic(1080);
forwardedPort.Exception += (sender, args) => Console.WriteLine(args.Exception.ToString());
client.AddForwardedPort(forwardedPort);
forwardedPort.Start();
var socksClient = new Socks5Handler(new IPEndPoint(IPAddress.Loopback, 1080),
string.Empty,
string.Empty);
var socksSocket = socksClient.Connect(new IPEndPoint(ipv4, 80));
socksSocket.Send(httpGetRequest);
var httpResponse = GetHttpResponse(socksSocket, Encoding.ASCII);
Assert.IsTrue(httpResponse.Contains(searchText), httpResponse);
forwardedPort.Dispose();
// check if client socket was properly closed
Assert.AreEqual(0, socksSocket.Receive(new byte[1], 0, 1, SocketFlags.None));
}
}
/// <summary>
/// Verifies whether channels are effectively closed.
/// </summary>
[TestMethod]
public void Ssh_LocalPortForwardingCloseChannels()
{
const string hostNameAlias = "localportforwarding-test.for.sshnet";
const string hostName = "github.com";
var ipAddress = Dns.GetHostAddresses(hostName)[0];
var hostsFileUpdated = AddOrUpdateHostsEntry(_adminConnectionInfoFactory, ipAddress, hostNameAlias);
try
{
var connectionInfo = _connectionInfoFactory.Create();
connectionInfo.MaxSessions = 1;
using (var client = new SshClient(connectionInfo))
{
client.Connect();
var localEndPoint = new IPEndPoint(IPAddress.Loopback, 1225);
for (var i = 0; i < (connectionInfo.MaxSessions + 1); i++)
{
var forwardedPort = new ForwardedPortLocal(localEndPoint.Address.ToString(),
(uint)localEndPoint.Port,
hostNameAlias,
80);
client.AddForwardedPort(forwardedPort);
forwardedPort.Start();
try
{
var httpRequest = (HttpWebRequest) WebRequest.Create("http://" + localEndPoint);
httpRequest.Host = hostName;
httpRequest.Method = "GET";
httpRequest.AllowAutoRedirect = false;
try
{
using (var httpResponse = (HttpWebResponse)httpRequest.GetResponse())
{
Assert.AreEqual(HttpStatusCode.MovedPermanently, httpResponse.StatusCode);
}
}
catch (WebException ex)
{
Assert.AreEqual(WebExceptionStatus.ProtocolError, ex.Status);
Assert.IsNotNull(ex.Response);
using (var httpResponse = ex.Response as HttpWebResponse)
{
Assert.IsNotNull(httpResponse);
Assert.AreEqual(HttpStatusCode.MovedPermanently, httpResponse.StatusCode);
}
}
}
finally
{
client.RemoveForwardedPort(forwardedPort);
}
}
}
}
finally
{
if (hostsFileUpdated)
{
RemoveHostsEntry(_adminConnectionInfoFactory, ipAddress, hostNameAlias);
}
}
}
[TestMethod]
public void Ssh_LocalPortForwarding()
{
const string hostNameAlias = "localportforwarding-test.for.sshnet";
const string hostName = "github.com";
var ipAddress = Dns.GetHostAddresses(hostName)[0];
var hostsFileUpdated = AddOrUpdateHostsEntry(_adminConnectionInfoFactory, ipAddress, hostNameAlias);
try
{
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
var localEndPoint = new IPEndPoint(IPAddress.Loopback, 1225);
var forwardedPort = new ForwardedPortLocal(localEndPoint.Address.ToString(),
(uint)localEndPoint.Port,
hostNameAlias,
80);
forwardedPort.Exception +=
(sender, args) => Console.WriteLine(@"ForwardedPort exception: " + args.Exception);
client.AddForwardedPort(forwardedPort);
forwardedPort.Start();
try
{
var httpRequest = (HttpWebRequest) WebRequest.Create("http://" + localEndPoint);
httpRequest.Host = hostName;
httpRequest.Method = "GET";
httpRequest.Accept = "text/html";
httpRequest.AllowAutoRedirect = false;
try
{
using (var httpResponse = (HttpWebResponse)httpRequest.GetResponse())
{
Assert.AreEqual(HttpStatusCode.MovedPermanently, httpResponse.StatusCode);
}
}
catch (WebException ex)
{
Assert.AreEqual(WebExceptionStatus.ProtocolError, ex.Status);
Assert.IsNotNull(ex.Response);
using (var httpResponse = ex.Response as HttpWebResponse)
{
Assert.IsNotNull(httpResponse);
Assert.AreEqual(HttpStatusCode.MovedPermanently, httpResponse.StatusCode);
}
}
}
finally
{
client.RemoveForwardedPort(forwardedPort);
}
}
}
finally
{
if (hostsFileUpdated)
{
RemoveHostsEntry(_adminConnectionInfoFactory, ipAddress, hostNameAlias);
}
}
}
[TestMethod]
public void Ssh_RemotePortForwarding()
{
var hostAddresses = Dns.GetHostAddresses(Dns.GetHostName());
var ipv4HostAddress = hostAddresses.First(p => p.AddressFamily == AddressFamily.InterNetwork);
var endpoint1 = new IPEndPoint(ipv4HostAddress, 666);
var endpoint2 = new IPEndPoint(ipv4HostAddress, 667);
var bytesReceivedOnListener1 = new List<byte>();
var bytesReceivedOnListener2 = new List<byte>();
using (var socketListener1 = new AsyncSocketListener(endpoint1))
using (var socketListener2 = new AsyncSocketListener(endpoint2))
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
socketListener1.BytesReceived += (received, socket) => bytesReceivedOnListener1.AddRange(received);
socketListener1.Start();
socketListener2.BytesReceived += (received, socket) => bytesReceivedOnListener2.AddRange(received);
socketListener2.Start();
client.Connect();
var forwardedPort1 = new ForwardedPortRemote(IPAddress.Loopback,
10000,
endpoint1.Address,
(uint)endpoint1.Port);
forwardedPort1.Exception += (sender, args) => Console.WriteLine(@"forwardedPort1 exception: " + args.Exception);
client.AddForwardedPort(forwardedPort1);
forwardedPort1.Start();
var forwardedPort2 = new ForwardedPortRemote(IPAddress.Loopback,
10001,
endpoint2.Address,
(uint)endpoint2.Port);
forwardedPort2.Exception += (sender, args) => Console.WriteLine(@"forwardedPort2 exception: " + args.Exception);
client.AddForwardedPort(forwardedPort2);
forwardedPort2.Start();
using (var s = client.CreateShellStream("a", 80, 25, 800, 600, 200))
{
s.WriteLine($"telnet {forwardedPort1.BoundHost} {forwardedPort1.BoundPort}");
s.Expect($"Connected to {forwardedPort1.BoundHost}\r\n");
s.WriteLine("ABC");
s.Flush();
s.Expect("ABC");
s.Close();
}
using (var s = client.CreateShellStream("b", 80, 25, 800, 600, 200))
{
s.WriteLine($"telnet {forwardedPort2.BoundHost} {forwardedPort2.BoundPort}");
s.Expect($"Connected to {forwardedPort2.BoundHost}\r\n");
s.WriteLine("DEF");
s.Flush();
s.Expect("DEF");
s.Close();
}
forwardedPort1.Stop();
forwardedPort2.Stop();
}
var textReceivedOnListener1 = Encoding.ASCII.GetString(bytesReceivedOnListener1.ToArray());
Assert.AreEqual("ABC\r\n", textReceivedOnListener1);
var textReceivedOnListener2 = Encoding.ASCII.GetString(bytesReceivedOnListener2.ToArray());
Assert.AreEqual("DEF\r\n", textReceivedOnListener2);
}
/// <summary>
/// Issue 1591
/// </summary>
[TestMethod]
public void Ssh_ExecuteShellScript()
{
const string remoteFile = "/home/sshnet/run.sh";
const string content = "#\bin\bash\necho Hello World!";
using (var client = new SftpClient(_connectionInfoFactory.Create()))
{
client.Connect();
if (client.Exists(remoteFile))
{
client.DeleteFile(remoteFile);
}
using (var memoryStream = new MemoryStream())
using (var sw = new StreamWriter(memoryStream, Encoding.ASCII))
{
sw.Write(content);
sw.Flush();
memoryStream.Position = 0;
client.UploadFile(memoryStream, remoteFile);
}
}
using (var client = new SshClient(_connectionInfoFactory.Create()))
{
client.Connect();
try
{
var runChmod = client.RunCommand("chmod u+x " + remoteFile);
runChmod.Execute();
Assert.AreEqual(0, runChmod.ExitStatus, runChmod.Error);
var runLs = client.RunCommand("ls " + remoteFile);
var asyncResultLs = runLs.BeginExecute();
var runScript = client.RunCommand(remoteFile);
var asyncResultScript = runScript.BeginExecute();
Assert.IsTrue(asyncResultScript.AsyncWaitHandle.WaitOne(10000));
var resultScript = runScript.EndExecute(asyncResultScript);
Assert.AreEqual("Hello World!\n", resultScript);
Assert.IsTrue(asyncResultLs.AsyncWaitHandle.WaitOne(10000));
var resultLs = runLs.EndExecute(asyncResultLs);
Assert.AreEqual(remoteFile + "\n", resultLs);
}
finally
{
RemoveFileOrDirectory(client, remoteFile);
}
}
}
/// <summary>
/// Verifies if a hosts file contains an entry for a given combination of IP address and hostname,
/// and if necessary add either the host entry or an alias to an exist entry for the specified IP
/// address.
/// </summary>
/// <param name="linuxAdminConnectionFactory"></param>
/// <param name="ipAddress"></param>
/// <param name="hostName"></param>
/// <returns>
/// <see langword="true"/> if an entry was added or updated in the specified hosts file; otherwise,
/// <see langword="false"/>.
/// </returns>
private static bool AddOrUpdateHostsEntry(IConnectionInfoFactory linuxAdminConnectionFactory,
IPAddress ipAddress,
string hostName)
{
const string hostsFile = "/etc/hosts";
using (var client = new ScpClient(linuxAdminConnectionFactory.Create()))
{
client.Connect();
var hostConfig = HostConfig.Read(client, hostsFile);
var hostEntry = hostConfig.Entries.SingleOrDefault(h => h.IPAddress.Equals(ipAddress));
if (hostEntry != null)
{
if (hostEntry.HostName == hostName)
{
return false;
}
foreach (var alias in hostEntry.Aliases)
{
if (alias == hostName)
{
return false;
}
}
hostEntry.Aliases.Add(hostName);
}
else
{
bool mappingFound = false;
for (var i = (hostConfig.Entries.Count - 1); i >= 0; i--)
{
hostEntry = hostConfig.Entries[i];
if (hostEntry.HostName == hostName)
{
if (hostEntry.IPAddress.Equals(ipAddress))
{
mappingFound = true;
continue;
}
// If hostname is currently mapped to another IP address, then remove the
// current mapping
hostConfig.Entries.RemoveAt(i);
}
else
{
for (var j = (hostEntry.Aliases.Count - 1); j >= 0; j--)
{
var alias = hostEntry.Aliases[j];
if (alias == hostName)
{
hostEntry.Aliases.RemoveAt(j);
}
}
}
}
if (!mappingFound)
{
hostEntry = new HostEntry(ipAddress, hostName);
hostConfig.Entries.Add(hostEntry);
}
}
hostConfig.Write(client, hostsFile);
return true;
}
}
/// <summary>
/// Remove the mapping between a given IP address and host name from the remote hosts file either by
/// removing a host entry entirely (if no other aliases are defined for the IP address) or removing
/// the aliases that match the host name for the IP address.
/// </summary>
/// <param name="linuxAdminConnectionFactory"></param>
/// <param name="ipAddress"></param>
/// <param name="hostName"></param>
/// <returns>
/// <see langword="true"/> if the hosts file was updated; otherwise, <see langword="false"/>.
/// </returns>
private static bool RemoveHostsEntry(IConnectionInfoFactory linuxAdminConnectionFactory,
IPAddress ipAddress,
string hostName)
{
const string hostsFile = "/etc/hosts";
using (var client = new ScpClient(linuxAdminConnectionFactory.Create()))
{
client.Connect();
var hostConfig = HostConfig.Read(client, hostsFile);
var hostEntry = hostConfig.Entries.SingleOrDefault(h => h.IPAddress.Equals(ipAddress));
if (hostEntry == null)
{
return false;
}
if (hostEntry.HostName == hostName)
{
if (hostEntry.Aliases.Count == 0)
{
hostConfig.Entries.Remove(hostEntry);
}
else
{
// Use one of the aliases (that are different from the specified host name) as host name
// of the host entry.
for (var i = hostEntry.Aliases.Count - 1; i >= 0; i--)
{
var alias = hostEntry.Aliases[i];
if (alias == hostName)
{
hostEntry.Aliases.RemoveAt(i);
}
else if (hostEntry.HostName == hostName)
{
// If we haven't already used one of the aliases as host name of the host entry
// then do this now and remove the alias.
hostEntry.HostName = alias;
hostEntry.Aliases.RemoveAt(i);
}
}
// If for some reason the host name of the host entry matched the specified host name
// and it only had aliases that match the host name, then remove the host entry altogether.
if (hostEntry.Aliases.Count == 0 && hostEntry.HostName == hostName)
{
hostConfig.Entries.Remove(hostEntry);
}
}
}
else
{
var aliasRemoved = false;
for (var i = hostEntry.Aliases.Count - 1; i >= 0; i--)
{
if (hostEntry.Aliases[i] == hostName)
{
hostEntry.Aliases.RemoveAt(i);
aliasRemoved = true;
}
}
if (!aliasRemoved)
{
return false;
}
}
hostConfig.Write(client, hostsFile);
return true;
}
}
private static string GetHttpResponse(Socket socket, Encoding encoding)
{
var httpResponseBuffer = new byte[2048];
// We expect:
// * The response to contain the searchText in the first receive.
// * The full response to be returned in the first receive.
var bytesReceived = socket.Receive(httpResponseBuffer,
0,
httpResponseBuffer.Length,
SocketFlags.None);
if (bytesReceived == 0)
{
return null;
}
if (bytesReceived == httpResponseBuffer.Length)
{
throw new Exception("We expect the HTTP response to be less than the buffer size. If not, we won't consume the full response.");
}
using (var sr = new StringReader(encoding.GetString(httpResponseBuffer, 0, bytesReceived)))
{
return sr.ReadToEnd();
}
}
private static void CreateShellScript(IConnectionInfoFactory connectionInfoFactory, string remoteFile, string script)
{
using (var sftpClient = new SftpClient(connectionInfoFactory.Create()))
{
sftpClient.Connect();
using (var sw = sftpClient.CreateText(remoteFile, new UTF8Encoding(false)))
{
sw.Write(script);
}
sftpClient.ChangePermissions(remoteFile, 0x1FF);
}
}
private static void RemoveFileOrDirectory(SshClient client, string remoteFile)
{
using (var cmd = client.CreateCommand("rm -Rf " + remoteFile))
{
cmd.Execute();
Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
}
}
}
}
@@ -0,0 +1,80 @@
using System.Security.Cryptography;
namespace Renci.SshNet.IntegrationTests
{
public abstract class TestBase : IntegrationTestBase
{
protected static MemoryStream CreateMemoryStream(int size)
{
var memoryStream = new MemoryStream();
FillStream(memoryStream, size);
return memoryStream;
}
protected static void FillStream(Stream stream, int size)
{
var randomContent = new byte[50];
var random = new Random();
var numberOfBytesToWrite = size;
while (numberOfBytesToWrite > 0)
{
random.NextBytes(randomContent);
var numberOfCharsToWrite = Math.Min(numberOfBytesToWrite, randomContent.Length);
stream.Write(randomContent, 0, numberOfCharsToWrite);
numberOfBytesToWrite -= numberOfCharsToWrite;
}
}
protected static string CreateHash(Stream stream)
{
MD5 md5 = new MD5CryptoServiceProvider();
var hash = md5.ComputeHash(stream);
return Encoding.ASCII.GetString(hash);
}
protected static string CreateHash(byte[] buffer)
{
using (var ms = new MemoryStream(buffer))
{
return CreateHash(ms);
}
}
protected static string CreateFileHash(string path)
{
using (var fs = File.OpenRead(path))
{
return CreateHash(fs);
}
}
protected static string CreateTempFile(int size)
{
var file = Path.GetTempFileName();
CreateFile(file, size);
return file;
}
protected static void CreateFile(string fileName, int size)
{
using (var fs = File.OpenWrite(fileName))
{
FillStream(fs, size);
}
}
protected Stream GetManifestResourceStream(string resourceName)
{
var type = GetType();
var resourceStream = type.Assembly.GetManifestResourceStream(resourceName);
if (resourceStream == null)
{
throw new ArgumentException($"Resource '{resourceName}' not found in assembly '{type.Assembly.FullName}'.", nameof(resourceName));
}
return resourceStream;
}
}
}
@@ -0,0 +1,19 @@
namespace Renci.SshNet.IntegrationTests
{
[TestClass]
public class TestInitializer
{
[AssemblyInitialize]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "MSTests requires context parameter")]
public static async Task Initialize(TestContext context)
{
await InfrastructureFixture.Instance.InitializeAsync();
}
[AssemblyCleanup]
public static async Task Cleanup()
{
await InfrastructureFixture.Instance.DisposeAsync();
}
}
}
@@ -0,0 +1,76 @@
using DotNet.Testcontainers.Builders;
using DotNet.Testcontainers.Containers;
using DotNet.Testcontainers.Images;
namespace Renci.SshNet.IntegrationTests.TestsFixtures
{
public sealed class InfrastructureFixture : IDisposable
{
private InfrastructureFixture()
{
}
private static readonly Lazy<InfrastructureFixture> InstanceLazy = new Lazy<InfrastructureFixture>(() => new InfrastructureFixture());
public static InfrastructureFixture Instance
{
get
{
return InstanceLazy.Value;
}
}
private IContainer _sshServer;
private IFutureDockerImage _sshServerImage;
public string SshServerHostName { get; set; }
public ushort SshServerPort { get; set; }
public SshUser AdminUser = new SshUser("sshnetadm", "ssh4ever");
public SshUser User = new SshUser("sshnet", "ssh4ever");
public async Task InitializeAsync()
{
_sshServerImage = new ImageFromDockerfileBuilder()
.WithName("renci-ssh-tests-server-image")
.WithDockerfileDirectory(CommonDirectoryPath.GetSolutionDirectory(), "Renci.SshNet.IntegrationTests")
.WithDockerfile("Dockerfile")
.WithDeleteIfExists(true)
.Build();
await _sshServerImage.CreateAsync();
_sshServer = new ContainerBuilder()
.WithHostname("renci-ssh-tests-server")
.WithImage(_sshServerImage)
.WithPortBinding(22, true)
.Build();
await _sshServer.StartAsync();
SshServerPort = _sshServer.GetMappedPublicPort(22);
SshServerHostName = _sshServer.Hostname;
}
public async Task DisposeAsync()
{
if (_sshServer != null)
{
await _sshServer.DisposeAsync();
}
if (_sshServerImage != null)
{
await _sshServerImage.DisposeAsync();
}
}
public void Dispose()
{
}
}
}
@@ -0,0 +1,85 @@
namespace Renci.SshNet.IntegrationTests.TestsFixtures
{
/// <summary>
/// The base class for integration tests
/// </summary>
public abstract class IntegrationTestBase
{
private readonly InfrastructureFixture _infrastructureFixture;
/// <summary>
/// The SSH Server host name.
/// </summary>
public string SshServerHostName
{
get
{
return _infrastructureFixture.SshServerHostName;
}
}
/// <summary>
/// The SSH Server host name
/// </summary>
public ushort SshServerPort
{
get
{
return _infrastructureFixture.SshServerPort;
}
}
/// <summary>
/// The admin user that can use SSH Server.
/// </summary>
public SshUser AdminUser
{
get
{
return _infrastructureFixture.AdminUser;
}
}
/// <summary>
/// The normal user that can use SSH Server.
/// </summary>
public SshUser User
{
get
{
return _infrastructureFixture.User;
}
}
protected IntegrationTestBase()
{
_infrastructureFixture = InfrastructureFixture.Instance;
ShowInfrastructureInformation();
}
private void ShowInfrastructureInformation()
{
Console.WriteLine($"SSH Server host name: {_infrastructureFixture.SshServerHostName}");
Console.WriteLine($"SSH Server port: {_infrastructureFixture.SshServerPort}");
}
/// <summary>
/// Creates the test file.
/// </summary>
/// <param name="fileName">Name of the file.</param>
/// <param name="size">Size in megabytes.</param>
protected void CreateTestFile(string fileName, int size)
{
using (var testFile = File.Create(fileName))
{
var random = new Random();
for (int i = 0; i < 1024 * size; i++)
{
var buffer = new byte[1024];
random.NextBytes(buffer);
testFile.Write(buffer, 0, buffer.Length);
}
}
}
}
}
@@ -0,0 +1,16 @@
namespace Renci.SshNet.IntegrationTests.TestsFixtures
{
public class SshUser
{
public string UserName { get; }
public string Password { get; }
public SshUser(string userName, string password)
{
UserName = userName;
Password = password;
}
}
}
@@ -0,0 +1,8 @@
namespace Renci.SshNet.IntegrationTests
{
internal static class Users
{
public static readonly Credential Regular = new Credential("sshnet", "ssh4ever");
public static readonly Credential Admin = new Credential("sshnetadm", "ssh4ever");
}
}
@@ -0,0 +1,5 @@
global using System.Text;
global using Microsoft.VisualStudio.TestTools.UnitTesting;
global using Renci.SshNet.IntegrationTests.TestsFixtures;

Some files were not shown because too many files have changed in this diff Show More