From 0f08b8502a71c92597fa20da6319fb14e91c4468 Mon Sep 17 00:00:00 2001 From: Gert Driesen Date: Sun, 15 Oct 2017 11:46:10 +0200 Subject: [PATCH 01/13] Prepare for 2016.1.0 RTW. --- build/nuget/SSH.NET.nuspec | 4 ++-- src/Renci.SshNet/Properties/CommonAssemblyInfo.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build/nuget/SSH.NET.nuspec b/build/nuget/SSH.NET.nuspec index 039a9e116..12bffe81e 100644 --- a/build/nuget/SSH.NET.nuspec +++ b/build/nuget/SSH.NET.nuspec @@ -2,7 +2,7 @@ SSH.NET - 2016.1.0-beta4 + 2016.1.0 SSH.NET Renci olegkap,drieseng @@ -10,7 +10,7 @@ https://github.com/sshnet/SSH.NET/ false SSH.NET is a Secure Shell (SSH) library for .NET, optimized for parallelism and with broad framework support. - https://github.com/sshnet/SSH.NET/releases/tag/2016.1.0-beta4 + https://github.com/sshnet/SSH.NET/releases/tag/2016.1.0 A Secure Shell (SSH) library for .NET, optimized for parallelism. 2012-2017, RENCI en-US diff --git a/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs b/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs index 8497d5fb0..2c41da970 100644 --- a/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs +++ b/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs @@ -11,7 +11,7 @@ [assembly: AssemblyVersion("2016.1.0")] [assembly: AssemblyFileVersion("2016.1.0")] -[assembly: AssemblyInformationalVersion("2016.1.0-beta4")] +[assembly: AssemblyInformationalVersion("2016.1.0")] [assembly: CLSCompliant(false)] // Setting ComVisible to false makes the types in this assembly not visible From 4583da528f93294f37f66d176cc23bfcb0d71e01 Mon Sep 17 00:00:00 2001 From: drieseng Date: Sun, 3 May 2020 17:46:40 +0200 Subject: [PATCH 02/13] Remove CWLs. --- src/Renci.SshNet/Session.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs index 058e589ab..897796850 100644 --- a/src/Renci.SshNet/Session.cs +++ b/src/Renci.SshNet/Session.cs @@ -1992,8 +1992,6 @@ private void MessageListener() // * a call to Disconnect() // * a call to Dispose() // * a SSH_MSG_DISCONNECT received from server - - Console.WriteLine("B"); break; } @@ -2002,7 +2000,6 @@ private void MessageListener() { // connection with SSH server was closed; // break out of the message loop - Console.WriteLine("C"); break; } @@ -2010,8 +2007,6 @@ private void MessageListener() message.Process(this); } - Console.WriteLine("D"); - // connection with SSH server was closed or socket was disposed RaiseError(CreateConnectionAbortedByServerException()); } From 66e24e7462e263069166d67f16c26a627262e375 Mon Sep 17 00:00:00 2001 From: Gert Driesen Date: Sun, 7 Jun 2020 10:52:12 +0200 Subject: [PATCH 03/13] Sponsoring (#691) * Create FUNDING.yml * Add sponsor info. --- .github/FUNDING.yml | 3 +++ README.md | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..81a8e9b30 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: sshnet diff --git a/README.md b/README.md index 0790a2bba..ebf9118f7 100644 --- a/README.md +++ b/README.md @@ -188,4 +188,6 @@ Visual Studio 2015 Update 3 | x | x | | Visual Studio 2017 | x | x | x | x | | | | | Visual Studio 2019 | x | x | x | x | | | | | -[![NDepend](http://download-codeplex.sec.s-msft.com/Download?ProjectName=sshnet&DownloadId=629750)](http://ndepend.com) +## Supporting SSH.NET + +Do you or your company rely on **SSH.NET** in your projects? If you 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. From 66cf838d264b1a03c05bedbdbbf72fe6654f4308 Mon Sep 17 00:00:00 2001 From: drieseng Date: Mon, 1 Jun 2020 10:03:51 +0200 Subject: [PATCH 04/13] Prepare for 2020.0.0-beta1 --- src/Renci.SshNet/Properties/CommonAssemblyInfo.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs b/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs index 2c41da970..cba256303 100644 --- a/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs +++ b/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs @@ -5,13 +5,13 @@ [assembly: AssemblyDescription("SSH.NET is a Secure Shell (SSH) library for .NET, optimized for parallelism.")] [assembly: AssemblyCompany("Renci")] [assembly: AssemblyProduct("SSH.NET")] -[assembly: AssemblyCopyright("Copyright © Renci 2010-2017")] +[assembly: AssemblyCopyright("Copyright © Renci 2010-2020")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("2016.1.0")] -[assembly: AssemblyFileVersion("2016.1.0")] -[assembly: AssemblyInformationalVersion("2016.1.0")] +[assembly: AssemblyVersion("2020.0.0")] +[assembly: AssemblyFileVersion("2020.0.0")] +[assembly: AssemblyInformationalVersion("2020.0.0-beta1")] [assembly: CLSCompliant(false)] // Setting ComVisible to false makes the types in this assembly not visible From d8fbae3c51bc489d8735e534df5143243a428fcc Mon Sep 17 00:00:00 2001 From: drieseng Date: Sun, 7 Jun 2020 13:23:50 +0200 Subject: [PATCH 05/13] Update name of Sandcastle Help File Builder environment variable. --- build/sandcastle/SSH.NET.shfbproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/sandcastle/SSH.NET.shfbproj b/build/sandcastle/SSH.NET.shfbproj index 50141f16e..539865294 100644 --- a/build/sandcastle/SSH.NET.shfbproj +++ b/build/sandcastle/SSH.NET.shfbproj @@ -36,7 +36,7 @@ {@HelpFormatOutputPaths} - + @@ -72,5 +72,5 @@ - + \ No newline at end of file From 32f26157957a99516f98f98eb2905b15f0fcf447 Mon Sep 17 00:00:00 2001 From: drieseng Date: Sun, 7 Jun 2020 15:31:29 +0200 Subject: [PATCH 06/13] Fix path to source file. --- src/Renci.SshNet/SshClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Renci.SshNet/SshClient.cs b/src/Renci.SshNet/SshClient.cs index b6b7cbb02..49c9ff84b 100644 --- a/src/Renci.SshNet/SshClient.cs +++ b/src/Renci.SshNet/SshClient.cs @@ -261,7 +261,7 @@ public SshCommand CreateCommand(string commandText, Encoding encoding) /// This method internally uses asynchronous calls. /// /// - /// + /// /// /// CommandText property is empty. /// Invalid Operation - An existing channel was used to execute this command. From bf651cac1eeb8f4f1594c74e9b90793d2f2602e9 Mon Sep 17 00:00:00 2001 From: drieseng Date: Sun, 7 Jun 2020 15:39:54 +0200 Subject: [PATCH 07/13] Remove local-use file. --- runtest.cmd | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 runtest.cmd diff --git a/runtest.cmd b/runtest.cmd deleted file mode 100644 index ad4c0f5ae..000000000 --- a/runtest.cmd +++ /dev/null @@ -1,3 +0,0 @@ -rem vstest.console src\Renci.SshNet.Tests\bin\Debug\net40\Renci.SshNet.Tests.dll "/TestCaseFilter:TestCategory=Gert - -vstest.console src\Renci.SshNet.Tests\bin\Debug\net40\Renci.SshNet.Tests.dll /TestCaseFilter:"TestCategory!=integration&TestCategory!=LongRunning" \ No newline at end of file From 853ec99609fbdb69ccd705fdefc3fa580237ce6a Mon Sep 17 00:00:00 2001 From: drieseng Date: Thu, 31 Dec 2020 13:06:47 +0100 Subject: [PATCH 08/13] Prepare for 2020.0.0 release. --- src/Renci.SshNet/Properties/CommonAssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs b/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs index cba256303..4941731e9 100644 --- a/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs +++ b/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs @@ -11,7 +11,7 @@ [assembly: AssemblyVersion("2020.0.0")] [assembly: AssemblyFileVersion("2020.0.0")] -[assembly: AssemblyInformationalVersion("2020.0.0-beta1")] +[assembly: AssemblyInformationalVersion("2020.0.0")] [assembly: CLSCompliant(false)] // Setting ComVisible to false makes the types in this assembly not visible From acda1431d23a9196377464e6f48056dfd42cc867 Mon Sep 17 00:00:00 2001 From: drieseng Date: Sun, 24 Jan 2021 14:40:24 +0100 Subject: [PATCH 09/13] Prepare for 2020.0.1 release --- src/Renci.SshNet/Properties/CommonAssemblyInfo.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs b/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs index 4941731e9..0883060cc 100644 --- a/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs +++ b/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs @@ -5,13 +5,13 @@ [assembly: AssemblyDescription("SSH.NET is a Secure Shell (SSH) library for .NET, optimized for parallelism.")] [assembly: AssemblyCompany("Renci")] [assembly: AssemblyProduct("SSH.NET")] -[assembly: AssemblyCopyright("Copyright © Renci 2010-2020")] +[assembly: AssemblyCopyright("Copyright © Renci 2010-2021")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("2020.0.0")] -[assembly: AssemblyFileVersion("2020.0.0")] -[assembly: AssemblyInformationalVersion("2020.0.0")] +[assembly: AssemblyVersion("2020.0.1")] +[assembly: AssemblyFileVersion("2020.0.1")] +[assembly: AssemblyInformationalVersion("2020.0.1")] [assembly: CLSCompliant(false)] // Setting ComVisible to false makes the types in this assembly not visible From 4cdedf65247a0e720f0723bcd9edb773b3f2223f Mon Sep 17 00:00:00 2001 From: drieseng Date: Sun, 29 May 2022 16:59:38 +0200 Subject: [PATCH 10/13] Use cryptographically secure random number generator. Fixes CVE-2022-29245. --- src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs index 9de7af8fb..b7a318bb9 100644 --- a/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs +++ b/src/Renci.SshNet/Security/KeyExchangeECCurve25519.cs @@ -1,5 +1,4 @@ -using System; -using Renci.SshNet.Abstractions; +using Renci.SshNet.Abstractions; using Renci.SshNet.Common; using Renci.SshNet.Messages.Transport; using Renci.SshNet.Security.Chaos.NaCl; @@ -46,9 +45,7 @@ public override void Start(Session session, KeyExchangeInitMessage message) var basepoint = new byte[MontgomeryCurve25519.PublicKeySizeInBytes]; basepoint[0] = 9; - var rnd = new Random(); - _privateKey = new byte[MontgomeryCurve25519.PrivateKeySizeInBytes]; - rnd.NextBytes(_privateKey); + _privateKey = CryptoAbstraction.GenerateRandom(MontgomeryCurve25519.PrivateKeySizeInBytes); _clientExchangeValue = new byte[MontgomeryCurve25519.PublicKeySizeInBytes]; MontgomeryOperations.scalarmult(_clientExchangeValue, 0, _privateKey, 0, basepoint, 0); From 69a42d3b708229e0f452d134e88337b1d8a9ee38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Nag=C3=B3rski?= Date: Thu, 29 Jun 2023 06:29:17 +0200 Subject: [PATCH 11/13] Integration tests --- .../.editorconfig | 32 ++++++++ src/Renci.SshNet.IntegrationTests/Dockerfile | 49 ++++++++++++ .../Renci.SshNet.IntegrationTests.csproj | 33 ++++++++ .../ScpClientTests.cs | 43 ++++++++++ .../SftpClientTests.cs | 67 ++++++++++++++++ .../SshClientTests.cs | 34 ++++++++ .../TestInitializer.cs | 20 +++++ .../TestsFixtures/InfrastructureFixture.cs | 80 +++++++++++++++++++ .../TestsFixtures/IntegrationTestBase.cs | 67 ++++++++++++++++ .../TestsFixtures/SshUser.cs | 16 ++++ src/Renci.SshNet.IntegrationTests/Usings.cs | 2 + .../server/script/start.sh | 10 +++ .../server/ssh/ssh_host_ecdsa_key | 9 +++ .../server/ssh/ssh_host_ed25519_key | 7 ++ .../server/ssh/ssh_host_rsa_key | 38 +++++++++ .../user/sshnet/authorized_keys | 4 + src/Renci.SshNet.sln | 22 +++++ 17 files changed, 533 insertions(+) create mode 100644 src/Renci.SshNet.IntegrationTests/.editorconfig create mode 100644 src/Renci.SshNet.IntegrationTests/Dockerfile create mode 100644 src/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj create mode 100644 src/Renci.SshNet.IntegrationTests/ScpClientTests.cs create mode 100644 src/Renci.SshNet.IntegrationTests/SftpClientTests.cs create mode 100644 src/Renci.SshNet.IntegrationTests/SshClientTests.cs create mode 100644 src/Renci.SshNet.IntegrationTests/TestInitializer.cs create mode 100644 src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs create mode 100644 src/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs create mode 100644 src/Renci.SshNet.IntegrationTests/TestsFixtures/SshUser.cs create mode 100644 src/Renci.SshNet.IntegrationTests/Usings.cs create mode 100644 src/Renci.SshNet.IntegrationTests/server/script/start.sh create mode 100644 src/Renci.SshNet.IntegrationTests/server/ssh/ssh_host_ecdsa_key create mode 100644 src/Renci.SshNet.IntegrationTests/server/ssh/ssh_host_ed25519_key create mode 100644 src/Renci.SshNet.IntegrationTests/server/ssh/ssh_host_rsa_key create mode 100644 src/Renci.SshNet.IntegrationTests/user/sshnet/authorized_keys diff --git a/src/Renci.SshNet.IntegrationTests/.editorconfig b/src/Renci.SshNet.IntegrationTests/.editorconfig new file mode 100644 index 000000000..b94e29112 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/.editorconfig @@ -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 diff --git a/src/Renci.SshNet.IntegrationTests/Dockerfile b/src/Renci.SshNet.IntegrationTests/Dockerfile new file mode 100644 index 000000000..811d51543 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/Dockerfile @@ -0,0 +1,49 @@ +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 && \ + echo 'PubkeyAcceptedAlgorithms ssh-rsa' >> /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"] \ No newline at end of file diff --git a/src/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj b/src/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj new file mode 100644 index 000000000..bf09d62cc --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj @@ -0,0 +1,33 @@ + + + + net7.0 + enable + enable + + false + true + + $(NoWarn);CS1591 + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + diff --git a/src/Renci.SshNet.IntegrationTests/ScpClientTests.cs b/src/Renci.SshNet.IntegrationTests/ScpClientTests.cs new file mode 100644 index 000000000..62cbaeb0b --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/ScpClientTests.cs @@ -0,0 +1,43 @@ +using System.Text; + +namespace Renci.SshNet.IntegrationTests +{ + /// + /// The SCP client integration tests + /// + [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 Scp_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(); + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/SftpClientTests.cs b/src/Renci.SshNet.IntegrationTests/SftpClientTests.cs new file mode 100644 index 000000000..5eb9a2dbe --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/SftpClientTests.cs @@ -0,0 +1,67 @@ +using System.Text; + +using Renci.SshNet.Common; + +namespace Renci.SshNet.IntegrationTests +{ + /// + /// The SFTP client integration tests + /// + [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 Test_Sftp_ListDirectory_Home_Directory() + { + var builder = new StringBuilder(); + var files = _sftpClient.ListDirectory("/"); + foreach (var file in files) + { + builder.AppendLine($"{file.FullName}"); + } + + Assert.AreEqual(@"/usr +/var +/. +/bin +/mnt +/opt +/tmp +/etc +/root +/media +/.. +/dev +/proc +/sys +/home +/lib +/sbin +/run +/srv +/.dockerenv +", 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(); + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/SshClientTests.cs b/src/Renci.SshNet.IntegrationTests/SshClientTests.cs new file mode 100644 index 000000000..ba25bc6c7 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/SshClientTests.cs @@ -0,0 +1,34 @@ +using System.Text; + +namespace Renci.SshNet.IntegrationTests +{ + /// + /// The SSH client integration tests + /// + [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 Test_SSH_Echo_Command() + { + var builder = new StringBuilder(); + var response = _sshClient.RunCommand("echo $'test !@#$%^&*()_+{}:,./<>[];\\|'"); + + Assert.AreEqual("test !@#$%^&*()_+{}:,./<>[];\\|\n", response.Result); + } + + public void Dispose() + { + _sshClient.Disconnect(); + _sshClient.Dispose(); + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/TestInitializer.cs b/src/Renci.SshNet.IntegrationTests/TestInitializer.cs new file mode 100644 index 000000000..5e0b7d876 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/TestInitializer.cs @@ -0,0 +1,20 @@ +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(); + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs b/src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs new file mode 100644 index 000000000..3c6a0572c --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs @@ -0,0 +1,80 @@ +using DotNet.Testcontainers.Images; +using DotNet.Testcontainers.Builders; +using DotNet.Testcontainers.Containers; + +namespace Renci.SshNet.IntegrationTests.TestsFixtures +{ + public sealed class InfrastructureFixture : IDisposable + { + private InfrastructureFixture() + { + + } + + private static readonly Lazy InstanceLazy = new Lazy(() => 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; } + + // TODO the user name and password can be injected to dockerfile via arguments + public SshUser AdminUser = new SshUser("sshnetadm", "ssh4ever"); + + // TODO the user name and password can be injected to dockerfile via arguments + 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) + //.WithPrivileged(true) + .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() + { + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs b/src/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs new file mode 100644 index 000000000..6079da1b5 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs @@ -0,0 +1,67 @@ + +namespace Renci.SshNet.IntegrationTests.TestsFixtures +{ + /// + /// The base class for integration tests + /// + public abstract class IntegrationTestBase + { + private readonly InfrastructureFixture _infrastructureFixture; + + /// + /// The SSH Server host name. + /// + public string? SshServerHostName + { + get + { + return _infrastructureFixture.SshServerHostName; + } + } + + /// + /// The SSH Server host name + /// + public ushort SshServerPort + { + get + { + return _infrastructureFixture.SshServerPort; + } + } + + /// + /// The admin user that can use SSH Server. + /// + public SshUser AdminUser + { + get + { + return _infrastructureFixture.AdminUser; + } + } + + /// + /// The normal user that can use SSH Server. + /// + 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}"); + } + } +} diff --git a/src/Renci.SshNet.IntegrationTests/TestsFixtures/SshUser.cs b/src/Renci.SshNet.IntegrationTests/TestsFixtures/SshUser.cs new file mode 100644 index 000000000..9a67f65c3 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/TestsFixtures/SshUser.cs @@ -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; + } + } +} + diff --git a/src/Renci.SshNet.IntegrationTests/Usings.cs b/src/Renci.SshNet.IntegrationTests/Usings.cs new file mode 100644 index 000000000..3d31edecb --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/Usings.cs @@ -0,0 +1,2 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using Renci.SshNet.IntegrationTests.TestsFixtures; diff --git a/src/Renci.SshNet.IntegrationTests/server/script/start.sh b/src/Renci.SshNet.IntegrationTests/server/script/start.sh new file mode 100644 index 000000000..e7e9f758c --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/server/script/start.sh @@ -0,0 +1,10 @@ +#!/bin/ash +/usr/sbin/syslog-ng + +# allow us to make changes to /etc/hosts; we need this for the port forwarding tests +chmod 777 /etc/hosts + +# start PAM-enabled ssh daemon as we also want keyboard-interactive authentication to work +/usr/sbin/sshd.pam + +tail -f < /var/log/auth.log diff --git a/src/Renci.SshNet.IntegrationTests/server/ssh/ssh_host_ecdsa_key b/src/Renci.SshNet.IntegrationTests/server/ssh/ssh_host_ecdsa_key new file mode 100644 index 000000000..5739844cf --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/server/ssh/ssh_host_ecdsa_key @@ -0,0 +1,9 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS +1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTtDoci0CaEgyR+p+ersiYltKUSqZx/ +MffWpnEPfGgnFI81huQw0D9e/SqABbeHtrzcSWskSZc0f2jjFxyqVkliAAAAsDrEln06xJ +Z9AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBO0OhyLQJoSDJH6n +56uyJiW0pRKpnH8x99amcQ98aCcUjzWG5DDQP179KoAFt4e2vNxJayRJlzR/aOMXHKpWSW +IAAAAgYdRMomjDSquRMSYTvEIzX7cReJ2grVIWsxIOLyhJnw0AAAAWcm9vdEBVYnVudHUx +OTEwRGVza3RvcAEC +-----END OPENSSH PRIVATE KEY----- diff --git a/src/Renci.SshNet.IntegrationTests/server/ssh/ssh_host_ed25519_key b/src/Renci.SshNet.IntegrationTests/server/ssh/ssh_host_ed25519_key new file mode 100644 index 000000000..8e45178e6 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/server/ssh/ssh_host_ed25519_key @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCCMW3VnuKfj6AxBQ7gJ4qAfEgw/YJl9q3AXyelCw+OdwAAAKDKcLC0ynCw +tAAAAAtzc2gtZWQyNTUxOQAAACCCMW3VnuKfj6AxBQ7gJ4qAfEgw/YJl9q3AXyelCw+Odw +AAAED9TRnDkG0tzdZv5oPJCXwzqrkxut7y33A8Wi8AzusJL4IxbdWe4p+PoDEFDuAnioB8 +SDD9gmX2rcBfJ6ULD453AAAAFnJvb3RAVWJ1bnR1MTkxMERlc2t0b3ABAgMEBQYH +-----END OPENSSH PRIVATE KEY----- diff --git a/src/Renci.SshNet.IntegrationTests/server/ssh/ssh_host_rsa_key b/src/Renci.SshNet.IntegrationTests/server/ssh/ssh_host_rsa_key new file mode 100644 index 000000000..edb94f341 --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/server/ssh/ssh_host_rsa_key @@ -0,0 +1,38 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +-----END OPENSSH PRIVATE KEY----- diff --git a/src/Renci.SshNet.IntegrationTests/user/sshnet/authorized_keys b/src/Renci.SshNet.IntegrationTests/user/sshnet/authorized_keys new file mode 100644 index 000000000..484a0bdcc --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/user/sshnet/authorized_keys @@ -0,0 +1,4 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4N0T6VopnwPSYQUw0waQNhBjz0DYFQwvkv4OwWYSf//fJF3M6bH42Tn2J+IlQ+4/fCFnE3m99seV5T1myEj7fsupNteY2sKFGXENLGtAD/76FM0iBmXx76xlSTyZSSmNDIRU4xUR23cfc+al84F5mO2lEk+5Zr3Qn5JUpucBfis4vtu0sMDgZ4w1d0tcuXkT/MEJn2iX2cnxbSy5qNAPHu7b+LEfXBv2OrMDqPrx/X6QREgi3w5RxL5kz7bvitWsIwIvb3ST2ARAArBwb8pEyp2A/w5p22rkQtL+3ibZ8fkmpgn33f31AZPgtM++iJPBmPKFjArcWEJ9fIVB/6DAj +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPzzrPpItEjNG7tU0DpJJ4pkI01E9d6K61OKTVPdFQSyGCdMj9XdP93lC6sJA+9/ahvf5F3gWEKxUJL2CKUiFWw= +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBLSsu/HNKiaALhQ26UDv+N0AFdMb26fMVrOKe866CGu6ajSf9HUOhJFdjhseihB2rTalMPr8MrcXNLufii4mL8u4l9fUQXFgwnM/ZpiVPSs6C+8i4u/ZDg7Nx2NXybNIgQ== +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACB4WgRgGBRo6Uk+cRgg8tJPCbEtGURRWlUA7PDDerXR+P9O6mm3L99Etxsyh5XNYqXyaMNtH5c51ooMajrFwcayAHIhPPb8X3CsTwEfIUQ96aDyHQMotbRfnkn6uefeUTRrSNcqeAndUtVyAqBdqbsq2mgJYXHrz2NUKlPFYgauQi+WQ== \ No newline at end of file diff --git a/src/Renci.SshNet.sln b/src/Renci.SshNet.sln index 09953551d..c73ed0faa 100644 --- a/src/Renci.SshNet.sln +++ b/src/Renci.SshNet.sln @@ -42,6 +42,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{D21A4D03-0 ..\test\Directory.Build.props = ..\test\Directory.Build.props EndProjectSection EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Renci.SshNet.IntegrationTests", "Renci.SshNet.IntegrationTests\Renci.SshNet.IntegrationTests.csproj", "{EEF98046-729C-419E-932D-4E569073C8CC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -84,6 +86,26 @@ Global {C45379B9-17B1-4E89-BC2E-6D41726413E8}.Release|Mixed Platforms.Build.0 = Release|Any CPU {C45379B9-17B1-4E89-BC2E-6D41726413E8}.Release|x64.ActiveCfg = Release|Any CPU {C45379B9-17B1-4E89-BC2E-6D41726413E8}.Release|x86.ActiveCfg = Release|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Debug|ARM.ActiveCfg = Debug|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Debug|ARM.Build.0 = Debug|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Debug|x64.ActiveCfg = Debug|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Debug|x64.Build.0 = Debug|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Debug|x86.ActiveCfg = Debug|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Debug|x86.Build.0 = Debug|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Release|Any CPU.Build.0 = Release|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Release|ARM.ActiveCfg = Release|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Release|ARM.Build.0 = Release|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Release|x64.ActiveCfg = Release|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Release|x64.Build.0 = Release|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Release|x86.ActiveCfg = Release|Any CPU + {EEF98046-729C-419E-932D-4E569073C8CC}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 6f567ec7bb2c190eaf10df3b4b36e25d752deab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Nag=C3=B3rski?= Date: Thu, 29 Jun 2023 06:31:15 +0200 Subject: [PATCH 12/13] Remove todos --- .../TestsFixtures/InfrastructureFixture.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs b/src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs index 3c6a0572c..340410ff0 100644 --- a/src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs +++ b/src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs @@ -29,10 +29,8 @@ public static InfrastructureFixture Instance public ushort SshServerPort { get; set; } - // TODO the user name and password can be injected to dockerfile via arguments public SshUser AdminUser = new SshUser("sshnetadm", "ssh4ever"); - // TODO the user name and password can be injected to dockerfile via arguments public SshUser User = new SshUser("sshnet", "ssh4ever"); public async Task InitializeAsync() From 82dc77a2f8c74318c87e095a0f946ffde98ad678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Nag=C3=B3rski?= Date: Wed, 30 Aug 2023 06:10:28 +0200 Subject: [PATCH 13/13] Use correct SSH.NET --- ...onTests.csproj => IntegrationTests.csproj} | 26 ++++-- .../ScpClientTests.cs | 6 +- .../SftpClientTests.cs | 89 +++++++++++++------ .../SshClientTests.cs | 6 +- .../TestInitializer.cs | 3 +- .../TestsFixtures/InfrastructureFixture.cs | 4 +- .../TestsFixtures/IntegrationTestBase.cs | 3 +- .../TestsFixtures/SshUser.cs | 2 +- src/Renci.SshNet.IntegrationTests/Usings.cs | 36 +++++++- src/Renci.SshNet.IntegrationTests/app.config | 15 ++++ .../Renci.SshNet.Tests.csproj | 6 +- src/Renci.SshNet.sln | 2 +- .../Properties/CommonAssemblyInfo.cs | 10 +-- 13 files changed, 148 insertions(+), 60 deletions(-) rename src/Renci.SshNet.IntegrationTests/{Renci.SshNet.IntegrationTests.csproj => IntegrationTests.csproj} (65%) create mode 100644 src/Renci.SshNet.IntegrationTests/app.config diff --git a/src/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj b/src/Renci.SshNet.IntegrationTests/IntegrationTests.csproj similarity index 65% rename from src/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj rename to src/Renci.SshNet.IntegrationTests/IntegrationTests.csproj index bf09d62cc..244ee6f3a 100644 --- a/src/Renci.SshNet.IntegrationTests/Renci.SshNet.IntegrationTests.csproj +++ b/src/Renci.SshNet.IntegrationTests/IntegrationTests.csproj @@ -1,4 +1,4 @@ - + net7.0 @@ -7,6 +7,7 @@ false true + true $(NoWarn);CS1591 + - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all + + + LocalSshNet + + + + + + PreserveNewest + + + diff --git a/src/Renci.SshNet.IntegrationTests/ScpClientTests.cs b/src/Renci.SshNet.IntegrationTests/ScpClientTests.cs index 62cbaeb0b..a03b04a3a 100644 --- a/src/Renci.SshNet.IntegrationTests/ScpClientTests.cs +++ b/src/Renci.SshNet.IntegrationTests/ScpClientTests.cs @@ -1,6 +1,4 @@ -using System.Text; - -namespace Renci.SshNet.IntegrationTests +namespace IntegrationTests { /// /// The SCP client integration tests @@ -18,7 +16,7 @@ public ScpClientTests() [TestMethod] - public void Scp_Upload_And_Download_FileStream() + public void Upload_And_Download_FileStream() { var file = $"/tmp/{Guid.NewGuid()}.txt"; var fileContent = "File content !@#$%^&*()_+{}:,./<>[];'\\|"; diff --git a/src/Renci.SshNet.IntegrationTests/SftpClientTests.cs b/src/Renci.SshNet.IntegrationTests/SftpClientTests.cs index 5eb9a2dbe..dfb406475 100644 --- a/src/Renci.SshNet.IntegrationTests/SftpClientTests.cs +++ b/src/Renci.SshNet.IntegrationTests/SftpClientTests.cs @@ -1,8 +1,4 @@ -using System.Text; - -using Renci.SshNet.Common; - -namespace Renci.SshNet.IntegrationTests +namespace IntegrationTests { /// /// The SFTP client integration tests @@ -19,35 +15,72 @@ public SftpClientTests() } [TestMethod] - public void Test_Sftp_ListDirectory_Home_Directory() + 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 = await _sftpClient.ListDirectoryAsync(testDirectory, CancellationToken.None); + + _sftpClient.DeleteFile(testFilePath); + _sftpClient.DeleteDirectory(testDirectory); + var builder = new StringBuilder(); - var files = _sftpClient.ListDirectory("/"); foreach (var file in files) { - builder.AppendLine($"{file.FullName}"); + builder.AppendLine($"{file.FullName} {file.IsRegularFile} {file.IsDirectory}"); } - Assert.AreEqual(@"/usr -/var -/. -/bin -/mnt -/opt -/tmp -/etc -/root -/media -/.. -/dev -/proc -/sys -/home -/lib -/sbin -/run -/srv -/.dockerenv + 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()); } diff --git a/src/Renci.SshNet.IntegrationTests/SshClientTests.cs b/src/Renci.SshNet.IntegrationTests/SshClientTests.cs index ba25bc6c7..33a90d13e 100644 --- a/src/Renci.SshNet.IntegrationTests/SshClientTests.cs +++ b/src/Renci.SshNet.IntegrationTests/SshClientTests.cs @@ -1,6 +1,4 @@ -using System.Text; - -namespace Renci.SshNet.IntegrationTests +namespace IntegrationTests { /// /// The SSH client integration tests @@ -17,7 +15,7 @@ public SshClientTests() } [TestMethod] - public void Test_SSH_Echo_Command() + public void Echo_Command_with_all_characters() { var builder = new StringBuilder(); var response = _sshClient.RunCommand("echo $'test !@#$%^&*()_+{}:,./<>[];\\|'"); diff --git a/src/Renci.SshNet.IntegrationTests/TestInitializer.cs b/src/Renci.SshNet.IntegrationTests/TestInitializer.cs index 5e0b7d876..16b0a3eca 100644 --- a/src/Renci.SshNet.IntegrationTests/TestInitializer.cs +++ b/src/Renci.SshNet.IntegrationTests/TestInitializer.cs @@ -1,4 +1,4 @@ -namespace Renci.SshNet.IntegrationTests +namespace IntegrationTests { [TestClass] public class TestInitializer @@ -7,7 +7,6 @@ public class TestInitializer [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(); } diff --git a/src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs b/src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs index 340410ff0..63e6c70ce 100644 --- a/src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs +++ b/src/Renci.SshNet.IntegrationTests/TestsFixtures/InfrastructureFixture.cs @@ -2,13 +2,12 @@ using DotNet.Testcontainers.Builders; using DotNet.Testcontainers.Containers; -namespace Renci.SshNet.IntegrationTests.TestsFixtures +namespace IntegrationTests.TestsFixtures { public sealed class InfrastructureFixture : IDisposable { private InfrastructureFixture() { - } private static readonly Lazy InstanceLazy = new Lazy(() => new InfrastructureFixture()); @@ -48,7 +47,6 @@ public async Task InitializeAsync() _sshServer = new ContainerBuilder() .WithHostname("renci-ssh-tests-server") .WithImage(_sshServerImage) - //.WithPrivileged(true) .WithPortBinding(22, true) .Build(); diff --git a/src/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs b/src/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs index 6079da1b5..521f947cc 100644 --- a/src/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs +++ b/src/Renci.SshNet.IntegrationTests/TestsFixtures/IntegrationTestBase.cs @@ -1,5 +1,4 @@ - -namespace Renci.SshNet.IntegrationTests.TestsFixtures +namespace IntegrationTests.TestsFixtures { /// /// The base class for integration tests diff --git a/src/Renci.SshNet.IntegrationTests/TestsFixtures/SshUser.cs b/src/Renci.SshNet.IntegrationTests/TestsFixtures/SshUser.cs index 9a67f65c3..5f2ee4d56 100644 --- a/src/Renci.SshNet.IntegrationTests/TestsFixtures/SshUser.cs +++ b/src/Renci.SshNet.IntegrationTests/TestsFixtures/SshUser.cs @@ -1,4 +1,4 @@ -namespace Renci.SshNet.IntegrationTests.TestsFixtures +namespace IntegrationTests.TestsFixtures { public class SshUser { diff --git a/src/Renci.SshNet.IntegrationTests/Usings.cs b/src/Renci.SshNet.IntegrationTests/Usings.cs index 3d31edecb..bc21218d4 100644 --- a/src/Renci.SshNet.IntegrationTests/Usings.cs +++ b/src/Renci.SshNet.IntegrationTests/Usings.cs @@ -1,2 +1,36 @@ +#pragma warning disable IDE0005 + +extern alias LocalSshNet; + +global using System.Text; + global using Microsoft.VisualStudio.TestTools.UnitTesting; -global using Renci.SshNet.IntegrationTests.TestsFixtures; + +global using IntegrationTests.TestsFixtures; + +// The testcontainers library uses SSH.NET, so we have two versions of SSH.NET in the project. +// We need to explicitly choose which version we want to test. +// To avoid problems, we import all namespaces. +global using LocalSshNet::Renci.SshNet; +global using LocalSshNet::Renci.SshNet.Abstractions; +global using LocalSshNet::Renci.SshNet.Channels; +global using LocalSshNet::Renci.SshNet.Common; +global using LocalSshNet::Renci.SshNet.Compression; +global using LocalSshNet::Renci.SshNet.Connection; +global using LocalSshNet::Renci.SshNet.Messages; +global using LocalSshNet::Renci.SshNet.Messages.Authentication; +global using LocalSshNet::Renci.SshNet.Messages.Connection; +global using LocalSshNet::Renci.SshNet.Messages.Transport; +global using LocalSshNet::Renci.SshNet.NetConf; +global using LocalSshNet::Renci.SshNet.Security; +global using LocalSshNet::Renci.SshNet.Security.Chaos; +global using LocalSshNet::Renci.SshNet.Security.Chaos.NaCl; +global using LocalSshNet::Renci.SshNet.Security.Chaos.NaCl.Internal; +global using LocalSshNet::Renci.SshNet.Security.Cryptography; +global using LocalSshNet::Renci.SshNet.Security.Cryptography.Ciphers; +global using LocalSshNet::Renci.SshNet.Security.Org; +global using LocalSshNet::Renci.SshNet.Security.Org.BouncyCastle; + +global using LocalSshNet::Renci.SshNet.Sftp; + + diff --git a/src/Renci.SshNet.IntegrationTests/app.config b/src/Renci.SshNet.IntegrationTests/app.config new file mode 100644 index 000000000..3571058ce --- /dev/null +++ b/src/Renci.SshNet.IntegrationTests/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj b/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj index 88693e5aa..94a4e8015 100644 --- a/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj +++ b/src/Renci.SshNet.Tests/Renci.SshNet.Tests.csproj @@ -64,9 +64,9 @@ - - - + + + diff --git a/src/Renci.SshNet.sln b/src/Renci.SshNet.sln index c73ed0faa..be2686bee 100644 --- a/src/Renci.SshNet.sln +++ b/src/Renci.SshNet.sln @@ -42,7 +42,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{D21A4D03-0 ..\test\Directory.Build.props = ..\test\Directory.Build.props EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Renci.SshNet.IntegrationTests", "Renci.SshNet.IntegrationTests\Renci.SshNet.IntegrationTests.csproj", "{EEF98046-729C-419E-932D-4E569073C8CC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTests", "Renci.SshNet.IntegrationTests\IntegrationTests.csproj", "{EEF98046-729C-419E-932D-4E569073C8CC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs b/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs index 0883060cc..d99338feb 100644 --- a/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs +++ b/src/Renci.SshNet/Properties/CommonAssemblyInfo.cs @@ -5,13 +5,13 @@ [assembly: AssemblyDescription("SSH.NET is a Secure Shell (SSH) library for .NET, optimized for parallelism.")] [assembly: AssemblyCompany("Renci")] [assembly: AssemblyProduct("SSH.NET")] -[assembly: AssemblyCopyright("Copyright © Renci 2010-2021")] +[assembly: AssemblyCopyright("Copyright © Renci 2010-2023")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: AssemblyVersion("2020.0.1")] -[assembly: AssemblyFileVersion("2020.0.1")] -[assembly: AssemblyInformationalVersion("2020.0.1")] +[assembly: AssemblyVersion("2023.0.0")] +[assembly: AssemblyFileVersion("2023.0.0")] +[assembly: AssemblyInformationalVersion("2023.0.0")] [assembly: CLSCompliant(false)] // Setting ComVisible to false makes the types in this assembly not visible @@ -24,4 +24,4 @@ [assembly: AssemblyConfiguration("Debug")] #else [assembly: AssemblyConfiguration("Release")] -#endif \ No newline at end of file +#endif