mirror of
https://github.com/sshnet/SSH.NET.git
synced 2026-09-10 01:05:42 +00:00
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:
committed by
GitHub
parent
4cdedf6524
commit
2bbfee4a37
+1246
File diff suppressed because it is too large
Load Diff
@@ -22,3 +22,6 @@ project.lock.json
|
||||
|
||||
# Build outputs
|
||||
build/target/
|
||||
|
||||
# Benchmark results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
* @drieseng @WojciechNagorski
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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 "%(VisualStudioVersionClassic.SolutionFile)""/>
|
||||
</Target>
|
||||
|
||||
<Target Name="RestoreNuGetPackagesModern" DependsOnTargets="DownloadNuGet" Outputs="%(VisualStudioVersionModern.Identity)">
|
||||
<Message Text="Restoring nuget packages for '%(VisualStudioVersionModern.SolutionFile)'..." Importance="High"/>
|
||||
<Exec Command="$(NuGetExe) restore "%(VisualStudioVersionModern.SolutionFile)""/>
|
||||
</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,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>
|
||||
@@ -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 |
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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-----
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
// }
|
||||
}
|
||||
}
|
||||
+41
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-18
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-24
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+263
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-13
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-14
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
-65
@@ -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
Reference in New Issue
Block a user