From 1ce30c619ec6764b10bfb3d21e6131f203d20b78 Mon Sep 17 00:00:00 2001 From: ALEKSANDRS ZELIKOVICS Date: Thu, 6 Oct 2022 13:14:31 +0300 Subject: [PATCH] #108 #102 + MemberValidationStatus : TooManyUsers --- FubarDev.FtpServer.sln | 1 + samples/TestFtpServer/TestFtpServer.csproj | 6 + samples/TestFtpServer/appsettings.json | 15 +- .../IMembershipProviderAsync.cs | 9 +- .../MemberValidationStatus.cs | 5 + .../Commands/DefaultFtpCommandDispatcher.cs | 5 +- .../Features/ISecureConnectionFeature.cs | 16 ++ .../FubarDev.FtpServer.Abstractions.csproj | 2 +- .../CommandHandlers/AppeCommandHandler.cs | 9 + .../CommandHandlers/AuthCommandHandler.cs | 9 +- .../CommandHandlers/DeleCommandHandler.cs | 9 + .../CommandHandlers/ListCommandHandler.cs | 9 + .../CommandHandlers/MdtmCommandHandler.cs | 9 + .../CommandHandlers/MlstCommandHandler.cs | 9 + .../CommandHandlers/PassCommandHandler.cs | 10 ++ .../CommandHandlers/RetrCommandHandler.cs | 9 + .../CommandHandlers/StorCommandHandler.cs | 9 + .../CommandHandlers/UserCommandHandler.cs | 10 ++ src/FubarDev.FtpServer/AuthTlsOptions.cs | 8 + .../DefaultSslStreamWrapperFactory.cs | 2 + .../Authentication/SslStreamExt.cs | 30 ++++ src/FubarDev.FtpServer/FtpConnection.cs | 19 ++ .../FubarDev.FtpServer.csproj | 36 +++- .../Networking/PausableFtpService.cs | 4 +- .../Networking/SemaphoreSlimExt.cs | 38 ++++ third-party/GnuSslStream/ReflectUtil.cs | 3 +- third-party/GnuSslStream/SslDirectCall.cs | 164 ++++++++++-------- 27 files changed, 357 insertions(+), 98 deletions(-) create mode 100644 src/FubarDev.FtpServer/Authentication/SslStreamExt.cs create mode 100644 src/FubarDev.FtpServer/Networking/SemaphoreSlimExt.cs diff --git a/FubarDev.FtpServer.sln b/FubarDev.FtpServer.sln index fef86b6c..d162e188 100644 --- a/FubarDev.FtpServer.sln +++ b/FubarDev.FtpServer.sln @@ -172,6 +172,7 @@ Global EndGlobalSection GlobalSection(SharedMSBuildProjectFiles) = preSolution src\FubarDev.FtpServer.Shared\FubarDev.FtpServer.Shared.projitems*{01ebbf94-7469-43a0-a618-07fb734b163f}*SharedItemsImports = 5 + third-party\GnuSslStream\GnuSslStream.projitems*{01ebbf94-7469-43a0-a618-07fb734b163f}*SharedItemsImports = 5 src\FubarDev.FtpServer.Shared\FubarDev.FtpServer.Shared.projitems*{37ce4218-fac3-4201-82a0-54945698c6ad}*SharedItemsImports = 5 src\FubarDev.FtpServer.Shared\FubarDev.FtpServer.Shared.projitems*{474bd381-5b80-4580-bfb1-d41beb70c458}*SharedItemsImports = 5 third-party\ReadLine\ReadLine.projitems*{4bda38ca-a19d-4ba9-ac7e-e0b091b3770d}*SharedItemsImports = 5 diff --git a/samples/TestFtpServer/TestFtpServer.csproj b/samples/TestFtpServer/TestFtpServer.csproj index aec832eb..3dfc89d3 100644 --- a/samples/TestFtpServer/TestFtpServer.csproj +++ b/samples/TestFtpServer/TestFtpServer.csproj @@ -7,6 +7,12 @@ ftpserver false + + $(DefineConstants)TRACE; + + + $(DefineConstants)TRACE; + diff --git a/samples/TestFtpServer/appsettings.json b/samples/TestFtpServer/appsettings.json index 789dc0ed..2d78764f 100644 --- a/samples/TestFtpServer/appsettings.json +++ b/samples/TestFtpServer/appsettings.json @@ -1,7 +1,7 @@ { "Serilog": { - "Using": ["Serilog.Sinks.Console"], - "MinimumLevel":{ + "Using": [ "Serilog.Sinks.Console" ], + "MinimumLevel": { "Default": "Debug", "Override": { "Microsoft": "Information", @@ -9,18 +9,19 @@ "FubarDev.FtpServer.CommandHandlers.MlstCommandHandler": "Verbose" } }, - "WriteTo": [ + "WriteTo": [ { "Name": "Console", "OutputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {ConnectionId} {Message:lj}{NewLine}{Exception}" } ], - "Enrich": ["FromLogContext"] + "Enrich": [ "FromLogContext" ] }, /* Supported authentication types are: "custom","anonymous", "pam", "default". * "default" means "not set" and is equal to "anonymous". */ - "authentication": "default", + // "authentication": "default", + "authentication": "custom", /* Sets the user id and group id for file system operations when * authentication is "pam" and "backend" is "unix". */ @@ -76,11 +77,11 @@ "ftps": { /* Path to the X.509 certificate. * It may either be a certificate or a PKCS#12-file with private key. */ - "certificate": null, + "certificate": "C:\\Users\\alex\\Desktop\\cert\\alexander.abc.pfx", /* Path to private key for the certificate. */ "privateKey": null, /* Password used to decrypt the PFX file. */ - "password": null, + "password": "123", /* Use implicit AUTH TLS? */ "implicit": false }, diff --git a/src/FubarDev.FtpServer.Abstractions/AccountManagement/IMembershipProviderAsync.cs b/src/FubarDev.FtpServer.Abstractions/AccountManagement/IMembershipProviderAsync.cs index 7fcec967..fb9fbf30 100644 --- a/src/FubarDev.FtpServer.Abstractions/AccountManagement/IMembershipProviderAsync.cs +++ b/src/FubarDev.FtpServer.Abstractions/AccountManagement/IMembershipProviderAsync.cs @@ -23,10 +23,7 @@ public interface IMembershipProviderAsync : IMembershipProvider /// The password. /// The . /// The result of the validation. - Task ValidateUserAsync( - string username, - string password, - CancellationToken cancellationToken = default); + Task ValidateUserAsync(string username, string password, CancellationToken cancellationToken = default); /// /// Logout of the given . @@ -34,8 +31,6 @@ Task ValidateUserAsync( /// The principal to be logged out. /// The . /// The task. - Task LogOutAsync( - ClaimsPrincipal principal, - CancellationToken cancellationToken = default); + Task LogOutAsync(ClaimsPrincipal principal, CancellationToken cancellationToken = default); } } diff --git a/src/FubarDev.FtpServer.Abstractions/AccountManagement/MemberValidationStatus.cs b/src/FubarDev.FtpServer.Abstractions/AccountManagement/MemberValidationStatus.cs index da696d96..852cf6fd 100644 --- a/src/FubarDev.FtpServer.Abstractions/AccountManagement/MemberValidationStatus.cs +++ b/src/FubarDev.FtpServer.Abstractions/AccountManagement/MemberValidationStatus.cs @@ -31,5 +31,10 @@ public enum MemberValidationStatus /// User authenticated successfully. /// AuthenticatedUser, + + /// + /// The too many users + /// + TooManyUsers, } } diff --git a/src/FubarDev.FtpServer.Abstractions/Commands/DefaultFtpCommandDispatcher.cs b/src/FubarDev.FtpServer.Abstractions/Commands/DefaultFtpCommandDispatcher.cs index 1658ad0c..da9443bf 100644 --- a/src/FubarDev.FtpServer.Abstractions/Commands/DefaultFtpCommandDispatcher.cs +++ b/src/FubarDev.FtpServer.Abstractions/Commands/DefaultFtpCommandDispatcher.cs @@ -179,9 +179,8 @@ private async Task SendResponseAsync(IFtpResponse? response, CancellationToken c } var serverCommandFeature = _connection.Features.Get(); - await serverCommandFeature.ServerCommandWriter - .WriteAsync(new SendResponseServerCommand(response), cancellationToken) - .ConfigureAwait(false); + await serverCommandFeature.ServerCommandWriter.WriteAsync(new SendResponseServerCommand(response), cancellationToken).ConfigureAwait(false); + if (response.Code == 421) { // Critical Error: We have to close the connection! diff --git a/src/FubarDev.FtpServer.Abstractions/Features/ISecureConnectionFeature.cs b/src/FubarDev.FtpServer.Abstractions/Features/ISecureConnectionFeature.cs index 5e1469a4..c1c8a33b 100644 --- a/src/FubarDev.FtpServer.Abstractions/Features/ISecureConnectionFeature.cs +++ b/src/FubarDev.FtpServer.Abstractions/Features/ISecureConnectionFeature.cs @@ -28,5 +28,21 @@ public interface ISecureConnectionFeature /// /// This doesn't apply to encrypted data streams. CloseEncryptedStreamDelegate CloseEncryptedControlStream { get; set; } + + /// + /// Checks the security. + /// + /// The error mess. + /// The connection. + /// IFtpResponse. + public IFtpResponse CheckSecurity(string errorMess, IFtpConnection connection); + + /// + /// Gets or sets a value indicating whether this instance is secure. + /// + /// + /// true if this instance is secure; otherwise, false. + /// + public bool IsSecure { get; set; } } } diff --git a/src/FubarDev.FtpServer.Abstractions/FubarDev.FtpServer.Abstractions.csproj b/src/FubarDev.FtpServer.Abstractions/FubarDev.FtpServer.Abstractions.csproj index d58c1db1..2327fe0f 100644 --- a/src/FubarDev.FtpServer.Abstractions/FubarDev.FtpServer.Abstractions.csproj +++ b/src/FubarDev.FtpServer.Abstractions/FubarDev.FtpServer.Abstractions.csproj @@ -1,7 +1,7 @@  - netstandard2.0;netstandard2.1 + netstandard2.0;netstandard2.1;net472 Interfaces for the portable FTP server FubarDev.FtpServer portable;FTP;server diff --git a/src/FubarDev.FtpServer.Commands/CommandHandlers/AppeCommandHandler.cs b/src/FubarDev.FtpServer.Commands/CommandHandlers/AppeCommandHandler.cs index 78ebcf92..38df843e 100644 --- a/src/FubarDev.FtpServer.Commands/CommandHandlers/AppeCommandHandler.cs +++ b/src/FubarDev.FtpServer.Commands/CommandHandlers/AppeCommandHandler.cs @@ -38,6 +38,15 @@ public AppeCommandHandler( /// public override async Task Process(FtpCommand command, CancellationToken cancellationToken) { + var secureConnectionFeature = Connection.Features.Get(); + + var isSecureResponse = secureConnectionFeature.CheckSecurity(T("Please use TLS connection"), Connection); + + if (isSecureResponse != null) + { + return isSecureResponse; + } + var restartPosition = Connection.Features.Get()?.RestartPosition; Connection.Features.Set(null); diff --git a/src/FubarDev.FtpServer.Commands/CommandHandlers/AuthCommandHandler.cs b/src/FubarDev.FtpServer.Commands/CommandHandlers/AuthCommandHandler.cs index 44320a62..7ba7847a 100644 --- a/src/FubarDev.FtpServer.Commands/CommandHandlers/AuthCommandHandler.cs +++ b/src/FubarDev.FtpServer.Commands/CommandHandlers/AuthCommandHandler.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using FubarDev.FtpServer.Commands; +using FubarDev.FtpServer.Features; using Microsoft.Extensions.DependencyInjection; @@ -21,7 +22,13 @@ public class AuthCommandHandler : FtpCommandHandler public override Task Process(FtpCommand command, CancellationToken cancellationToken) { var loginStateMachine = Connection.ConnectionServices.GetRequiredService(); - return loginStateMachine.ExecuteAsync(command, cancellationToken); + + var res = loginStateMachine.ExecuteAsync(command, cancellationToken); + + var secureConnectionFeature = Connection.Features.Get(); + secureConnectionFeature.IsSecure = true; + + return res; } } } diff --git a/src/FubarDev.FtpServer.Commands/CommandHandlers/DeleCommandHandler.cs b/src/FubarDev.FtpServer.Commands/CommandHandlers/DeleCommandHandler.cs index dece32f2..5352e5be 100644 --- a/src/FubarDev.FtpServer.Commands/CommandHandlers/DeleCommandHandler.cs +++ b/src/FubarDev.FtpServer.Commands/CommandHandlers/DeleCommandHandler.cs @@ -37,6 +37,15 @@ public DeleCommandHandler(ILogger? logger = null) /// public override async Task Process(FtpCommand command, CancellationToken cancellationToken) { + var secureConnectionFeature = Connection.Features.Get(); + + var isSecureResponse = secureConnectionFeature.CheckSecurity(T("Please use TLS connection"), Connection); + + if (isSecureResponse != null) + { + return isSecureResponse; + } + var path = command.Argument; var fsFeature = Connection.Features.Get(); var currentPath = fsFeature.Path.Clone(); diff --git a/src/FubarDev.FtpServer.Commands/CommandHandlers/ListCommandHandler.cs b/src/FubarDev.FtpServer.Commands/CommandHandlers/ListCommandHandler.cs index 7ea23bf0..d89812eb 100644 --- a/src/FubarDev.FtpServer.Commands/CommandHandlers/ListCommandHandler.cs +++ b/src/FubarDev.FtpServer.Commands/CommandHandlers/ListCommandHandler.cs @@ -49,6 +49,15 @@ public ListCommandHandler( /// public override async Task Process(FtpCommand command, CancellationToken cancellationToken) { + var secureConnectionFeature = Connection.Features.Get(); + + var isSecureResponse = secureConnectionFeature.CheckSecurity(T("Please use TLS connection"), Connection); + + if (isSecureResponse != null) + { + return isSecureResponse; + } + await FtpContext.ServerCommandWriter .WriteAsync( new SendResponseServerCommand(new FtpResponse(150, T("Opening data connection."))), diff --git a/src/FubarDev.FtpServer.Commands/CommandHandlers/MdtmCommandHandler.cs b/src/FubarDev.FtpServer.Commands/CommandHandlers/MdtmCommandHandler.cs index d7b79b23..f0b0af42 100644 --- a/src/FubarDev.FtpServer.Commands/CommandHandlers/MdtmCommandHandler.cs +++ b/src/FubarDev.FtpServer.Commands/CommandHandlers/MdtmCommandHandler.cs @@ -24,6 +24,15 @@ public class MdtmCommandHandler : FtpCommandHandler /// public override async Task Process(FtpCommand command, CancellationToken cancellationToken) { + var secureConnectionFeature = Connection.Features.Get(); + + var isSecureResponse = secureConnectionFeature.CheckSecurity(T("Please use TLS connection"), Connection); + + if (isSecureResponse != null) + { + return isSecureResponse; + } + var path = command.Argument; var fsFeature = Connection.Features.Get(); var currentPath = fsFeature.Path.Clone(); diff --git a/src/FubarDev.FtpServer.Commands/CommandHandlers/MlstCommandHandler.cs b/src/FubarDev.FtpServer.Commands/CommandHandlers/MlstCommandHandler.cs index dfca3785..23eba89f 100644 --- a/src/FubarDev.FtpServer.Commands/CommandHandlers/MlstCommandHandler.cs +++ b/src/FubarDev.FtpServer.Commands/CommandHandlers/MlstCommandHandler.cs @@ -90,6 +90,15 @@ internal static IMlstFactsFeature CreateMlstFactsFeature() private async Task ProcessMlstAsync(FtpCommand command, CancellationToken cancellationToken) { + var secureConnectionFeature = Connection.Features.Get(); + + var isSecureResponse = secureConnectionFeature.CheckSecurity(T("Please use TLS connection"), Connection); + + if (isSecureResponse != null) + { + return isSecureResponse; + } + var argument = command.Argument; var fsFeature = Connection.Features.Get(); var path = fsFeature.Path.Clone(); diff --git a/src/FubarDev.FtpServer.Commands/CommandHandlers/PassCommandHandler.cs b/src/FubarDev.FtpServer.Commands/CommandHandlers/PassCommandHandler.cs index 5210b8a9..b1f18228 100644 --- a/src/FubarDev.FtpServer.Commands/CommandHandlers/PassCommandHandler.cs +++ b/src/FubarDev.FtpServer.Commands/CommandHandlers/PassCommandHandler.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using FubarDev.FtpServer.Commands; +using FubarDev.FtpServer.Features; using Microsoft.Extensions.DependencyInjection; @@ -23,6 +24,15 @@ public class PassCommandHandler : FtpCommandHandler /// public override Task Process(FtpCommand command, CancellationToken cancellationToken) { + var secureConnectionFeature = Connection.Features.Get(); + + var isSecureResponse = secureConnectionFeature.CheckSecurity(T("Please use TLS connection"), Connection); + + if (isSecureResponse != null) + { + return Task.FromResult(isSecureResponse); + } + var loginStateMachine = Connection.ConnectionServices.GetRequiredService(); return loginStateMachine.ExecuteAsync(command, cancellationToken); } diff --git a/src/FubarDev.FtpServer.Commands/CommandHandlers/RetrCommandHandler.cs b/src/FubarDev.FtpServer.Commands/CommandHandlers/RetrCommandHandler.cs index a5b0df32..9a591479 100644 --- a/src/FubarDev.FtpServer.Commands/CommandHandlers/RetrCommandHandler.cs +++ b/src/FubarDev.FtpServer.Commands/CommandHandlers/RetrCommandHandler.cs @@ -42,6 +42,15 @@ public RetrCommandHandler( /// public override async Task Process(FtpCommand command, CancellationToken cancellationToken) { + var secureConnectionFeature = Connection.Features.Get(); + + var isSecureResponse = secureConnectionFeature.CheckSecurity(T("Please use TLS connection"), Connection); + + if (isSecureResponse != null) + { + return isSecureResponse; + } + var restartPosition = Connection.Features.Get()?.RestartPosition; Connection.Features.Set(null); diff --git a/src/FubarDev.FtpServer.Commands/CommandHandlers/StorCommandHandler.cs b/src/FubarDev.FtpServer.Commands/CommandHandlers/StorCommandHandler.cs index a1f2f016..74a33513 100644 --- a/src/FubarDev.FtpServer.Commands/CommandHandlers/StorCommandHandler.cs +++ b/src/FubarDev.FtpServer.Commands/CommandHandlers/StorCommandHandler.cs @@ -44,6 +44,15 @@ public StorCommandHandler( /// public override async Task Process(FtpCommand command, CancellationToken cancellationToken) { + var secureConnectionFeature = Connection.Features.Get(); + + var isSecureResponse = secureConnectionFeature.CheckSecurity(T("Please use TLS connection"), Connection); + + if (isSecureResponse != null) + { + return isSecureResponse; + } + var restartPosition = Connection.Features.Get()?.RestartPosition; Connection.Features.Set(null); diff --git a/src/FubarDev.FtpServer.Commands/CommandHandlers/UserCommandHandler.cs b/src/FubarDev.FtpServer.Commands/CommandHandlers/UserCommandHandler.cs index 25f26747..d2c4502a 100644 --- a/src/FubarDev.FtpServer.Commands/CommandHandlers/UserCommandHandler.cs +++ b/src/FubarDev.FtpServer.Commands/CommandHandlers/UserCommandHandler.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using FubarDev.FtpServer.Commands; +using FubarDev.FtpServer.Features; using Microsoft.Extensions.DependencyInjection; @@ -23,6 +24,15 @@ public class UserCommandHandler : FtpCommandHandler /// public override Task Process(FtpCommand command, CancellationToken cancellationToken) { + var secureConnectionFeature = Connection.Features.Get(); + + var isSecureResponse = secureConnectionFeature.CheckSecurity(T("Please use TLS connection"), Connection); + + if (isSecureResponse != null) + { + return Task.FromResult(isSecureResponse); + } + var loginStateMachine = Connection.ConnectionServices.GetRequiredService(); return loginStateMachine.ExecuteAsync(command, cancellationToken); } diff --git a/src/FubarDev.FtpServer/AuthTlsOptions.cs b/src/FubarDev.FtpServer/AuthTlsOptions.cs index 2add3608..1250efdd 100644 --- a/src/FubarDev.FtpServer/AuthTlsOptions.cs +++ b/src/FubarDev.FtpServer/AuthTlsOptions.cs @@ -20,5 +20,13 @@ public class AuthTlsOptions /// Gets or sets a value indicating whether implicit FTPS is used. /// public bool ImplicitFtps { get; set; } + + /// + /// Gets or sets a value indicating whether [only secure connection]. + /// + /// + /// true if [only secure connection]; otherwise, false. + /// + public bool OnlySecureConnection { get; set; } } } diff --git a/src/FubarDev.FtpServer/Authentication/DefaultSslStreamWrapperFactory.cs b/src/FubarDev.FtpServer/Authentication/DefaultSslStreamWrapperFactory.cs index 30c1f999..4c820dfe 100644 --- a/src/FubarDev.FtpServer/Authentication/DefaultSslStreamWrapperFactory.cs +++ b/src/FubarDev.FtpServer/Authentication/DefaultSslStreamWrapperFactory.cs @@ -4,6 +4,7 @@ using System; using System.IO; +using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Threading; @@ -39,6 +40,7 @@ public async Task WrapStreamAsync( { try { + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; _logger?.LogTrace("Create SSL stream"); var sslStream = CreateSslStream(unencryptedStream, keepOpen); try diff --git a/src/FubarDev.FtpServer/Authentication/SslStreamExt.cs b/src/FubarDev.FtpServer/Authentication/SslStreamExt.cs new file mode 100644 index 00000000..fa657187 --- /dev/null +++ b/src/FubarDev.FtpServer/Authentication/SslStreamExt.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; +using System.Threading; + +namespace FubarDev.FtpServer.Authentication +{ + namespace System.Net.Security + { + public class NegotiateStream : AuthenticatedStream + { + public override Task FlushAsync(CancellationToken cancellationToken); + } + public class SslStream : AuthenticatedStream + { + public virtual void AuthenticateAsClient(string targetHost, X509CertificateCollection clientCertificates, bool checkCertificateRevocation); + public virtual Task AuthenticateAsClientAsync(string targetHost, X509CertificateCollection clientCertificates, bool checkCertificateRevocation); + public virtual void AuthenticateAsServer(X509Certificate serverCertificate, bool clientCertificateRequired, bool checkCertificateRevocation); + public virtual Task AuthenticateAsServerAsync(X509Certificate serverCertificate, bool clientCertificateRequired, bool checkCertificateRevocation); + public virtual IAsyncResult BeginAuthenticateAsClient(string targetHost, X509CertificateCollection clientCertificates, bool checkCertificateRevocation, AsyncCallback asyncCallback, object asyncState); + public virtual IAsyncResult BeginAuthenticateAsServer(X509Certificate serverCertificate, bool clientCertificateRequired, bool checkCertificateRevocation, AsyncCallback asyncCallback, object asyncState); + public override Task FlushAsync(CancellationToken cancellationToken); + public virtual Task ShutdownAsync(); + } + } +} diff --git a/src/FubarDev.FtpServer/FtpConnection.cs b/src/FubarDev.FtpServer/FtpConnection.cs index d86f6ac4..d123f764 100644 --- a/src/FubarDev.FtpServer/FtpConnection.cs +++ b/src/FubarDev.FtpServer/FtpConnection.cs @@ -1030,6 +1030,25 @@ private class SecureConnectionFeature : ISecureConnectionFeature /// public CloseEncryptedStreamDelegate CloseEncryptedControlStream { get; set; } = ct => Task.CompletedTask; + + /// + /// Gets or sets a value indicating whether this instance is secure. + /// + /// + /// true if this instance is secure; otherwise, false. + /// + public bool IsSecure { get; set; } + + /// + /// Checks the security. + /// + /// FtpResponse if response not TLS. + public IFtpResponse CheckSecurity(string errorMess, IFtpConnection connection) + { + var authTlsOptions = connection.ConnectionServices.GetRequiredService>(); + + return authTlsOptions.Value?.OnlySecureConnection == true && !this.IsSecure ? new FtpResponse(550, errorMess) : null; + } } private class DuplexPipe : IDuplexPipe diff --git a/src/FubarDev.FtpServer/FubarDev.FtpServer.csproj b/src/FubarDev.FtpServer/FubarDev.FtpServer.csproj index 295ef36e..95cf125e 100644 --- a/src/FubarDev.FtpServer/FubarDev.FtpServer.csproj +++ b/src/FubarDev.FtpServer/FubarDev.FtpServer.csproj @@ -1,10 +1,43 @@  - netstandard2.0;netstandard2.1 + + netstandard2.1;net472;net471 TCP-based FTP server implementation portable;FTP;server + True + + $(DefineConstants)TRACE;USE_SYNC_SSL_STREAM;NETCOREAPP;USE_GNU_SSL_STREAM + + + $(DefineConstants)TRACE;USE_SYNC_SSL_STREAM;NETCOREAPP;USE_GNU_SSL_STREAM + + + $(DefineConstants)TRACE;USE_GNU_SSL_STREAM;USE_SYNC_SSL_STREAM;NETCOREAPP + + + $(DefineConstants)TRACE;USE_GNU_SSL_STREAM;USE_SYNC_SSL_STREAM;NETCOREAPP + + + $(DefineConstants)TRACE;USE_GNU_SSL_STREAM;USE_SYNC_SSL_STREAM;NET47 + + + $(DefineConstants)TRACE;USE_GNU_SSL_STREAM;USE_SYNC_SSL_STREAM;NET47 + + + $(DefineConstants)TRACE;USE_GNU_SSL_STREAM;USE_SYNC_SSL_STREAM;NET47 + + + $(DefineConstants)TRACE;USE_GNU_SSL_STREAM;USE_SYNC_SSL_STREAM;NET47 + + + + + + + + @@ -13,4 +46,5 @@ + diff --git a/src/FubarDev.FtpServer/Networking/PausableFtpService.cs b/src/FubarDev.FtpServer/Networking/PausableFtpService.cs index 389af331..f9973f53 100644 --- a/src/FubarDev.FtpServer/Networking/PausableFtpService.cs +++ b/src/FubarDev.FtpServer/Networking/PausableFtpService.cs @@ -7,6 +7,8 @@ using System.Threading; using System.Threading.Tasks; +using Abc.FubarDev.FtpServer.Networking; + using Microsoft.Extensions.Logging; namespace FubarDev.FtpServer.Networking @@ -71,7 +73,7 @@ public virtual async Task StartAsync(CancellationToken cancellationToken) throw new InvalidOperationException($"Status must be {FtpServiceStatus.ReadyToRun}, but was {Status}."); } - using var semaphore = new SemaphoreSlim(0, 1); + using var semaphore = new SemaphoreSlimExt(0, 1); _jobPaused = new CancellationTokenSource(); _task = RunAsync( new Progress( diff --git a/src/FubarDev.FtpServer/Networking/SemaphoreSlimExt.cs b/src/FubarDev.FtpServer/Networking/SemaphoreSlimExt.cs new file mode 100644 index 00000000..f7c8d4ff --- /dev/null +++ b/src/FubarDev.FtpServer/Networking/SemaphoreSlimExt.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) Fubar Development Junker. All rights reserved. +// + +using System.Threading; + +namespace Abc.FubarDev.FtpServer.Networking +{ + internal class SemaphoreSlimExt : SemaphoreSlim + { + /// + /// Initializes a new instance of the class. + /// + /// The initial number of requests for the semaphore that can be granted concurrently. + public SemaphoreSlimExt(int initialCount) + : base(initialCount) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The initial number of requests for the semaphore that can be granted concurrently. + /// The maximum number of requests for the semaphore that can be granted concurrently. + public SemaphoreSlimExt(int initialCount, int maxCount) + : base(initialCount, maxCount) + { + } + + public bool IsDisposed { get; internal set; } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + IsDisposed = true; + } + } +} diff --git a/third-party/GnuSslStream/ReflectUtil.cs b/third-party/GnuSslStream/ReflectUtil.cs index c236287b..0824fd3c 100644 --- a/third-party/GnuSslStream/ReflectUtil.cs +++ b/third-party/GnuSslStream/ReflectUtil.cs @@ -12,8 +12,7 @@ internal static class ReflectUtil public static object GetField(object obj, string fieldName) { var tp = obj.GetType(); - var info = GetAllFields(tp) - .Where(f => f.Name == fieldName).SingleOrDefault(); + var info = GetAllFields(tp)?.Where(f => f.Name == fieldName)?.FirstOrDefault(); return info?.GetValue(obj); } diff --git a/third-party/GnuSslStream/SslDirectCall.cs b/third-party/GnuSslStream/SslDirectCall.cs index bc800a5a..f725f0e4 100644 --- a/third-party/GnuSslStream/SslDirectCall.cs +++ b/third-party/GnuSslStream/SslDirectCall.cs @@ -10,112 +10,126 @@ internal unsafe static class SslDirectCall { public static void CloseNotify(SslStream sslStream) { - if (sslStream.IsAuthenticated) + try { - bool isServer = sslStream.IsServer; - - byte[] result; - int resultSz; - - int SCHANNEL_SHUTDOWN = 1; - var workArray = BitConverter.GetBytes(SCHANNEL_SHUTDOWN); + if (sslStream.IsAuthenticated) + { + bool isServer = sslStream.IsServer; - var nativeApiHelpers = GetNativeApiHelpers(sslStream); - var (securityContextHandle, credentialsHandleHandle) = nativeApiHelpers.GetHandlesFunc(); + byte[] result; + int resultSz; - int bufferSize = 1; - NativeApi.SecurityBufferDescriptor securityBufferDescriptor = new NativeApi.SecurityBufferDescriptor(bufferSize); - NativeApi.SecurityBufferStruct[] unmanagedBuffer = new NativeApi.SecurityBufferStruct[bufferSize]; + int SCHANNEL_SHUTDOWN = 1; + var workArray = BitConverter.GetBytes(SCHANNEL_SHUTDOWN); - fixed (NativeApi.SecurityBufferStruct* ptr = unmanagedBuffer) - fixed (void* workArrayPtr = workArray) - { - securityBufferDescriptor.UnmanagedPointer = (void*)ptr; + var nativeApiHelpers = GetNativeApiHelpers(sslStream); + var (securityContextHandle, credentialsHandleHandle) = nativeApiHelpers.GetHandlesFunc(); - unmanagedBuffer[0].token = (IntPtr)workArrayPtr; - unmanagedBuffer[0].count = workArray.Length; - unmanagedBuffer[0].type = NativeApi.BufferType.Token; + int bufferSize = 1; + NativeApi.SecurityBufferDescriptor securityBufferDescriptor = new NativeApi.SecurityBufferDescriptor(bufferSize); + NativeApi.SecurityBufferStruct[] unmanagedBuffer = new NativeApi.SecurityBufferStruct[bufferSize]; - NativeApi.SecurityStatus status; - status = (NativeApi.SecurityStatus)NativeApi.ApplyControlToken(ref securityContextHandle, securityBufferDescriptor); - if (status == NativeApi.SecurityStatus.OK) + fixed (NativeApi.SecurityBufferStruct* ptr = unmanagedBuffer) + fixed (void* workArrayPtr = workArray) { - unmanagedBuffer[0].token = IntPtr.Zero; - unmanagedBuffer[0].count = 0; - unmanagedBuffer[0].type = NativeApi.BufferType.Token; - - NativeApi.SSPIHandle contextHandleOut = default(NativeApi.SSPIHandle); - NativeApi.ContextFlags outflags = NativeApi.ContextFlags.Zero; - long ts = 0; + securityBufferDescriptor.UnmanagedPointer = (void*)ptr; - var inflags = NativeApi.ContextFlags.SequenceDetect | - NativeApi.ContextFlags.ReplayDetect | - NativeApi.ContextFlags.Confidentiality | - NativeApi.ContextFlags.AcceptExtendedError | - NativeApi.ContextFlags.AllocateMemory | - NativeApi.ContextFlags.InitStream; + unmanagedBuffer[0].token = (IntPtr)workArrayPtr; + unmanagedBuffer[0].count = workArray.Length; + unmanagedBuffer[0].type = NativeApi.BufferType.Token; - if (isServer) - { - status = (NativeApi.SecurityStatus)NativeApi.AcceptSecurityContext(ref credentialsHandleHandle, ref securityContextHandle, null, - inflags, NativeApi.Endianness.Native, ref contextHandleOut, securityBufferDescriptor, ref outflags, out ts); - } - else - { - status = (NativeApi.SecurityStatus)NativeApi.InitializeSecurityContextW(ref credentialsHandleHandle, ref securityContextHandle, null, - inflags, 0, NativeApi.Endianness.Native, null, 0, ref contextHandleOut, securityBufferDescriptor, ref outflags, out ts); - } + NativeApi.SecurityStatus status; + status = (NativeApi.SecurityStatus)NativeApi.ApplyControlToken(ref securityContextHandle, securityBufferDescriptor); if (status == NativeApi.SecurityStatus.OK) { - byte[] resultArr = new byte[unmanagedBuffer[0].count]; - Marshal.Copy(unmanagedBuffer[0].token, resultArr, 0, resultArr.Length); - Marshal.FreeCoTaskMem(unmanagedBuffer[0].token); - result = resultArr; - resultSz = resultArr.Length; + unmanagedBuffer[0].token = IntPtr.Zero; + unmanagedBuffer[0].count = 0; + unmanagedBuffer[0].type = NativeApi.BufferType.Token; + + NativeApi.SSPIHandle contextHandleOut = default(NativeApi.SSPIHandle); + NativeApi.ContextFlags outflags = NativeApi.ContextFlags.Zero; + long ts = 0; + + var inflags = NativeApi.ContextFlags.SequenceDetect | + NativeApi.ContextFlags.ReplayDetect | + NativeApi.ContextFlags.Confidentiality | + NativeApi.ContextFlags.AcceptExtendedError | + NativeApi.ContextFlags.AllocateMemory | + NativeApi.ContextFlags.InitStream; + + if (isServer) + { + status = (NativeApi.SecurityStatus)NativeApi.AcceptSecurityContext(ref credentialsHandleHandle, ref securityContextHandle, null, + inflags, NativeApi.Endianness.Native, ref contextHandleOut, securityBufferDescriptor, ref outflags, out ts); + } + else + { + status = (NativeApi.SecurityStatus)NativeApi.InitializeSecurityContextW(ref credentialsHandleHandle, ref securityContextHandle, null, + inflags, 0, NativeApi.Endianness.Native, null, 0, ref contextHandleOut, securityBufferDescriptor, ref outflags, out ts); + } + if (status == NativeApi.SecurityStatus.OK) + { + byte[] resultArr = new byte[unmanagedBuffer[0].count]; + Marshal.Copy(unmanagedBuffer[0].token, resultArr, 0, resultArr.Length); + Marshal.FreeCoTaskMem(unmanagedBuffer[0].token); + result = resultArr; + resultSz = resultArr.Length; + } + else + { + throw new InvalidOperationException(string.Format("AcceptSecurityContext/InitializeSecurityContextW returned [{0}] during CloseNotify.", status)); + } } else { - throw new InvalidOperationException(string.Format("AcceptSecurityContext/InitializeSecurityContextW returned [{0}] during CloseNotify.", status)); + throw new InvalidOperationException(string.Format("ApplyControlToken returned [{0}] during CloseNotify.", status)); } } - else - { - throw new InvalidOperationException(string.Format("ApplyControlToken returned [{0}] during CloseNotify.", status)); - } - } - var innerStream = nativeApiHelpers.GetInnerStreamFunc(); - innerStream.Write(result, 0, resultSz); + var innerStream = nativeApiHelpers.GetInnerStreamFunc(); + innerStream.Write(result, 0, resultSz); + } + } + catch (Exception e) + { + throw e; } } private static NativeApiHelpers GetNativeApiHelpers(SslStream sslStream) { - var sslState = ReflectUtil.GetField(sslStream, "_SslState"); - if (sslState != null) + try { - return new NativeApiHelpers() + var sslState = ReflectUtil.GetField(sslStream, "_SslState"); + if (sslState != null) { - GetHandlesFunc = () => GetHandlesForDotNetFramework(sslState), - GetInnerStreamFunc = () => (Stream)ReflectUtil.GetProperty(sslState, "InnerStream"), - }; - } + return new NativeApiHelpers() + { + GetHandlesFunc = () => GetHandlesForDotNetFramework(sslState), + GetInnerStreamFunc = () => (Stream)ReflectUtil.GetProperty(sslState, "InnerStream"), + }; + } + + sslState = ReflectUtil.GetField(sslStream, "_sslState"); + if (sslState != null) + { + return new NativeApiHelpers() + { + GetHandlesFunc = () => GetHandlesForNetCore10(sslStream), + GetInnerStreamFunc = () => (Stream)ReflectUtil.GetField(sslStream, "_innerStream"), + }; + } - sslState = ReflectUtil.GetField(sslStream, "_sslState"); - if (sslState != null) - { return new NativeApiHelpers() { - GetHandlesFunc = () => GetHandlesForNetCore10(sslStream), + GetHandlesFunc = () => GetHandlesForNetCore30(sslStream), GetInnerStreamFunc = () => (Stream)ReflectUtil.GetField(sslStream, "_innerStream"), }; } - - return new NativeApiHelpers() + catch (Exception e) { - GetHandlesFunc = () => GetHandlesForNetCore30(sslStream), - GetInnerStreamFunc = () => (Stream)ReflectUtil.GetField(sslStream, "_innerStream"), - }; + throw e; + } } private static (NativeApi.SSPIHandle securityContextHandle, NativeApi.SSPIHandle credentialsHandleHandle)