private async Task ProcessMessage(FtpCommand command)
        {
            FtpResponse response;

            Log?.Trace(command);
            var result = FindCommandHandler(command);

            if (result != null)
            {
                var handler         = result.Item2;
                var handlerCommand  = result.Item1;
                var isLoginRequired = result.Item3;
                if (isLoginRequired && !Data.IsLoggedIn)
                {
                    response = new FtpResponse(530, "Not logged in.");
                }
                else
                {
                    try
                    {
                        var cmdHandler  = handler as FtpCommandHandler;
                        var isAbortable = cmdHandler?.IsAbortable ?? false;
                        if (isAbortable)
                        {
                            var newBackgroundTask = Data.BackgroundCommandHandler.Execute(handler, handlerCommand);
                            if (newBackgroundTask != null)
                            {
                                _activeBackgroundTask = newBackgroundTask;
                                response = null;
                            }
                            else
                            {
                                response = new FtpResponse(503, "Parallel commands aren't allowed.");
                            }
                        }
                        else
                        {
                            response = await handler.Process(handlerCommand, _cancellationTokenSource.Token);
                        }
                    }
                    catch (Exception ex)
                    {
                        Log?.Error(ex, "Failed to process message ({0})", command);
                        response = new FtpResponse(501, "Syntax error in parameters or arguments.");
                    }
                }
            }
            else
            {
                response = new FtpResponse(500, "Syntax error, command unrecognized.");
            }
            if (response != null)
            {
                await WriteAsync(response, _cancellationTokenSource.Token);
            }
        }
 /// <summary>
 /// Writes a FTP response to a client
 /// </summary>
 /// <param name="response">The response to write to the client</param>
 internal void Write([NotNull] FtpResponse response)
 {
     if (!_closed)
     {
         Log?.Log(response);
         var data = Encoding.GetBytes($"{response}\r\n");
         SocketStream.Write(data, 0, data.Length);
         response.AfterWriteAction?.Invoke();
     }
 }
        /// <summary>
        /// Writes a FTP response to a client
        /// </summary>
        /// <param name="response">The response to write to the client</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>The task</returns>
        public async Task WriteAsync([NotNull] FtpResponse response, CancellationToken cancellationToken)
        {
            if (!_closed)
            {
                Log?.Log(response);
                var data = Encoding.GetBytes($"{response}\r\n");
                await SocketStream.WriteAsync(data, 0, data.Length, cancellationToken);

                response.AfterWriteAction?.Invoke();
            }
        }
 /// <summary>
 /// Logs a message with the data of the <see cref="FtpResponse"/>
 /// </summary>
 /// <param name="log">The <see cref="IFtpLog"/> to use</param>
 /// <param name="response">The <see cref="FtpResponse"/> to log</param>
 /// <remarks>
 /// It logs either a trace, debug, or warning message depending on the
 /// <see cref="FtpResponse.Code"/>.
 /// </remarks>
 public static void Log([NotNull] this IFtpLog log, [NotNull] FtpResponse response)
 {
     if (response.Code >= 200 && response.Code < 300)
     {
         log.Trace(response);
     }
     else if (response.Code >= 300 && response.Code < 400)
     {
         log.Info(response);
     }
     else if (response.Code < 200)
     {
         log.Debug(response);
     }
     else
     {
         log.Warn(response);
     }
 }
Exemple #5
0
        private async Task AddClientAsync(TcpClient client)
        {
            var scope = _serviceProvider.CreateScope();

            try
            {
                var socketAccessor = scope.ServiceProvider.GetRequiredService <TcpSocketClientAccessor>();
                socketAccessor.TcpSocketClient = client;

                var connection         = scope.ServiceProvider.GetRequiredService <IFtpConnection>();
                var connectionAccessor = scope.ServiceProvider.GetRequiredService <IFtpConnectionAccessor>();
                connectionAccessor.FtpConnection = connection;

                if (MaxActiveConnections != 0 && _statistics.ActiveConnections >= MaxActiveConnections)
                {
                    var response                = new FtpResponse(10068, "Too many users, server is full.");
                    var responseBuffer          = Encoding.UTF8.GetBytes($"{response}\r\n");
                    var secureConnectionFeature = connection.Features.Get <ISecureConnectionFeature>();
                    secureConnectionFeature.OriginalStream.Write(responseBuffer, 0, responseBuffer.Length);
                    client.Dispose();
                    scope.Dispose();
                    return;
                }

                if (!_connections.TryAdd(connection, new FtpConnectionInfo(scope)))
                {
                    scope.Dispose();
                    return;
                }

                _statistics.AddConnection();
                connection.Closed += ConnectionOnClosed;
                OnConfigureConnection(connection);
                await connection.StartAsync()
                .ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                scope.Dispose();
                _log?.LogError(ex, ex.Message);
            }
        }
Exemple #6
0
        private void AddClient(TcpClient client)
        {
            try
            {
                var scope          = _serviceProvider.CreateScope();
                var socketAccessor = scope.ServiceProvider.GetRequiredService <TcpSocketClientAccessor>();
                socketAccessor.TcpSocketClient = client;

                var connection         = scope.ServiceProvider.GetRequiredService <IFtpConnection>();
                var connectionAccessor = scope.ServiceProvider.GetRequiredService <IFtpConnectionAccessor>();
                connectionAccessor.FtpConnection = connection;

                if (MaxActiveConnections != 0 && _statistics.ActiveConnections >= MaxActiveConnections)
                {
                    var response = new FtpResponse(10068, "Too many users, server is full.");
                    connection.WriteAsync(response, CancellationToken.None).Wait();
                    client.Dispose();
                    scope.Dispose();
                    return;
                }

                if (!_connections.TryAdd(connection, new FtpConnectionInfo(scope)))
                {
                    scope.Dispose();
                    return;
                }

                _statistics.ActiveConnections += 1;
                _statistics.TotalConnections  += 1;
                connection.Closed             += ConnectionOnClosed;
                OnConfigureConnection(connection);
                connection.Start();
            }
            catch (Exception ex)
            {
                _log?.LogError(ex, ex.Message);
            }
        }
        public Task <FtpResponse> Execute([NotNull] FtpCommandHandlerBase handler, [NotNull] FtpCommand command)
        {
            Contract.Ensures(Contract.Result <Task <FtpResponse> >() != null);
            lock (_syncRoot)
            {
                if (_handlerTask != null)
                {
                    return(null);
                }

                _cancellationTokenSource = new CancellationTokenSource();
                _handlerTask             = handler.Process(command, _cancellationTokenSource.Token);
            }

            var taskCanceled = _handlerTask
                               .ContinueWith(
                t =>
            {
                var response = new FtpResponse(426, "Connection closed; transfer aborted.");
                Debug.WriteLine($"Background task cancelled with response {response}");
                return(response);
            },
                TaskContinuationOptions.OnlyOnCanceled);

            var taskCompleted = _handlerTask
                                .ContinueWith(
                t =>
            {
                var response = t.Result;
                Debug.WriteLine($"{DateTimeOffset.UtcNow} Background task finished successfully with response {response}");
                return(response);
            },
                TaskContinuationOptions.OnlyOnRanToCompletion);

            var taskFaulted = _handlerTask
                              .ContinueWith(
                t =>
            {
                var ex = t.Exception;
                _connection.Log?.Error(ex, "Error while processing background command {0}", command);
                var response = new FtpResponse(501, "Syntax error in parameters or arguments.");
                Debug.WriteLine($"Background task failed with response {response}");
                return(response);
            },
                TaskContinuationOptions.OnlyOnFaulted);

            taskFaulted.ContinueWith(t => { }, TaskContinuationOptions.OnlyOnCanceled);
            taskCompleted.ContinueWith(t => { }, TaskContinuationOptions.OnlyOnCanceled);
            taskCanceled.ContinueWith(t => { }, TaskContinuationOptions.OnlyOnCanceled);

            return(Task.Run(
                       () =>
            {
                var tasks = new List <Task <FtpResponse> > {
                    taskCompleted, taskCanceled, taskFaulted
                };

                do
                {
                    try
                    {
                        var waitTasks = tasks.Where(x => !x.IsCompleted).Cast <Task>().ToArray();
                        if (waitTasks.Length != 0)
                        {
                            Debug.WriteLine($"Waiting for {waitTasks.Length} background tasks");
                            Task.WaitAll(waitTasks);
                        }
                    }
                    catch (AggregateException ex)
                    {
                        ex.Handle(e => e is TaskCanceledException);
                    }
                }while (tasks.Any(t => !t.IsCompleted));

                var response = tasks.Single(x => x.Status == TaskStatus.RanToCompletion).Result;
                Debug.WriteLine($"{DateTimeOffset.UtcNow} Background task finished with response {response}");

                lock (_syncRoot)
                    _handlerTask = null;

                return response;
            }));
        }
Exemple #8
0
 /// <summary>
 /// Logs a warning message with the data of the <see cref="FtpResponse"/>.
 /// </summary>
 /// <param name="log">The <see cref="ILogger"/> to use.</param>
 /// <param name="response">The <see cref="FtpResponse"/> to log.</param>
 public static void Warn([NotNull] this ILogger log, [NotNull] FtpResponse response)
 {
     log.LogWarning("{code} {message}", response.Code, response.Message);
 }
Exemple #9
0
 /// <summary>
 /// Logs an error message with the data of the <see cref="FtpResponse"/>.
 /// </summary>
 /// <param name="log">The <see cref="ILogger"/> to use.</param>
 /// <param name="response">The <see cref="FtpResponse"/> to log.</param>
 public static void Error([NotNull] this ILogger log, [NotNull] FtpResponse response)
 {
     log.LogError("{code} {message}", response.Code, response.Message);
 }
Exemple #10
0
        private async Task AddClientAsync(TcpClient client)
        {
            var scope = _serviceProvider.CreateScope();

            try
            {
                Stream socketStream = client.GetStream();
                foreach (var controlStreamAdapter in _controlStreamAdapters)
                {
                    socketStream = await controlStreamAdapter.WrapAsync(socketStream, CancellationToken.None)
                                   .ConfigureAwait(false);
                }

                // Initialize information about the socket
                var socketAccessor = scope.ServiceProvider.GetRequiredService <TcpSocketClientAccessor>();
                socketAccessor.TcpSocketClient = client;
                socketAccessor.TcpSocketStream = socketStream;

                // Create the connection
                var connection         = scope.ServiceProvider.GetRequiredService <IFtpConnection>();
                var connectionAccessor = scope.ServiceProvider.GetRequiredService <IFtpConnectionAccessor>();
                connectionAccessor.FtpConnection = connection;

                // Remember connection
                if (!_connections.TryAdd(connection, new FtpConnectionInfo(scope)))
                {
                    _log.LogCritical("A new scope was created, but the connection couldn't be added to the list.");
                    client.Dispose();
                    scope.Dispose();
                    return;
                }

                // Send initial message
                var serverCommandWriter = connection.Features.Get <IServerCommandFeature>().ServerCommandWriter;

                var blockConnection = MaxActiveConnections != 0 &&
                                      _statistics.ActiveConnections >= MaxActiveConnections;
                if (blockConnection)
                {
                    // Send response
                    var response = new FtpResponse(421, "Too many users, server is full.");
                    await serverCommandWriter.WriteAsync(new SendResponseServerCommand(response))
                    .ConfigureAwait(false);

                    // Send close
                    await serverCommandWriter.WriteAsync(new CloseConnectionServerCommand())
                    .ConfigureAwait(false);
                }
                else
                {
                    var serverMessages = scope.ServiceProvider.GetRequiredService <IFtpServerMessages>();
                    var response       = new FtpResponseTextBlock(220, serverMessages.GetBannerMessage());

                    // Send initial response
                    await serverCommandWriter.WriteAsync(
                        new SendResponseServerCommand(response),
                        connection.CancellationToken)
                    .ConfigureAwait(false);
                }

                // Statistics
                _statistics.AddConnection();

                // Statistics and cleanup
                connection.Closed += ConnectionOnClosed;

                // Connection configuration by host
                var asyncInitFunctions = OnConfigureConnection(connection);
                foreach (var asyncInitFunction in asyncInitFunctions)
                {
                    await asyncInitFunction(connection, CancellationToken.None)
                    .ConfigureAwait(false);
                }

                // Start connection
                await connection.StartAsync()
                .ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                scope.Dispose();
                _log?.LogError(ex, ex.Message);
            }
        }
        /// <summary>
        /// Executes some code with error handling.
        /// </summary>
        /// <param name="connection">The connection to execute the code for.</param>
        /// <param name="command">The command to execute the code for.</param>
        /// <param name="commandAction">The action to be executed.</param>
        /// <param name="logger">The logger to be used for logging.</param>
        /// <param name="cancellationToken">The cancellation token to signal command abortion.</param>
        /// <returns>The task with the (optional) response.</returns>
        public static async Task <IFtpResponse?> ExecuteCommand(
            this IFtpConnection connection,
            FtpCommand command,
            Func <FtpCommand, CancellationToken, Task <IFtpResponse?> > commandAction,
            ILogger?logger,
            CancellationToken cancellationToken)
        {
            var          localizationFeature = connection.Features.Get <ILocalizationFeature>();
            IFtpResponse?response;

            try
            {
                response = await commandAction(command, cancellationToken)
                           .ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                var exception = ex;
                while (exception is AggregateException aggregateException)
                {
                    exception = aggregateException.InnerException;
                }

                switch (exception)
                {
                case ValidationException validationException:
                    response = new FtpResponse(
                        425,
                        validationException.Message);
                    logger?.LogWarning(validationException.Message);
                    break;

#if !NETSTANDARD1_3
                case SocketException se when se.ErrorCode == (int)SocketError.ConnectionAborted:
#endif
                case OperationCanceledException _:
                    response = new FtpResponse(426, localizationFeature.Catalog.GetString("Connection closed; transfer aborted."));
                    logger?.LogTrace("Command {command} cancelled with response {response}", command, response);
                    break;

                case FileSystemException fse:
                {
                    var message = fse.Message != null ? $"{fse.FtpErrorName}: {fse.Message}" : fse.FtpErrorName;
                    logger?.LogInformation("Rejected command ({command}) with error {code} {message}", command, fse.FtpErrorCode, message);
                    response = new FtpResponse(fse.FtpErrorCode, message);
                    break;
                }

                case NotSupportedException nse:
                {
                    var message = nse.Message ?? localizationFeature.Catalog.GetString("Command {command} not supported", command);
                    logger?.LogInformation(message);
                    response = new FtpResponse(502, message);
                    break;
                }

                default:
                    logger?.LogError(0, ex, "Failed to process message ({command})", command);
                    response = new FtpResponse(501, localizationFeature.Catalog.GetString("Syntax error in parameters or arguments."));
                    break;
                }
            }

            return(response);
        }
Exemple #12
0
        private async Task ProcessMessage(FtpCommand command)
        {
            FtpResponse response;

            Log?.Trace(command);
            var result = FindCommandHandler(command);

            if (result != null)
            {
                var handler         = result.Item2;
                var handlerCommand  = result.Item1;
                var isLoginRequired = result.Item3;
                if (isLoginRequired && !Data.IsLoggedIn)
                {
                    response = new FtpResponse(530, "Not logged in.");
                }
                else
                {
                    try
                    {
                        var cmdHandler  = handler as FtpCommandHandler;
                        var isAbortable = cmdHandler?.IsAbortable ?? false;
                        if (isAbortable)
                        {
                            var newBackgroundTask = Data.BackgroundCommandHandler.Execute(handler, handlerCommand);
                            if (newBackgroundTask != null)
                            {
                                _activeBackgroundTask = newBackgroundTask;
                                response = null;
                            }
                            else
                            {
                                response = new FtpResponse(503, "Parallel commands aren't allowed.");
                            }
                        }
                        else
                        {
                            response = await handler.Process(handlerCommand, _cancellationTokenSource.Token)
                                       .ConfigureAwait(false);
                        }
                    }
                    catch (FileSystemException fse)
                    {
                        var message = fse.Message != null ? $"{fse.FtpErrorName}: {fse.Message}" : fse.FtpErrorName;
                        Log?.LogInformation($"Rejected command ({command}) with error {fse.FtpErrorCode} {message}");
                        response = new FtpResponse(fse.FtpErrorCode, message);
                    }
                    catch (NotSupportedException nse)
                    {
                        var message = nse.Message ?? $"Command {command} not supported";
                        Log?.LogInformation(message);
                        response = new FtpResponse(502, message);
                    }
                    catch (Exception ex)
                    {
                        Log?.LogError(ex, "Failed to process message ({0})", command);
                        response = new FtpResponse(501, "Syntax error in parameters or arguments.");
                    }
                }
            }
            else
            {
                response = new FtpResponse(500, "Syntax error, command unrecognized.");
            }
            if (response != null)
            {
                await WriteAsync(response, _cancellationTokenSource.Token).ConfigureAwait(false);
            }
        }
 private async Task ProcessMessage(FtpCommand command)
 {
     FtpResponse response;
     Log?.Trace(command);
     var result = FindCommandHandler(command);
     if (result != null)
     {
         var handler = result.Item2;
         var handlerCommand = result.Item1;
         var isLoginRequired = result.Item3;
         if (isLoginRequired && !Data.IsLoggedIn)
         {
             response = new FtpResponse(530, "Not logged in.");
         }
         else
         {
             try
             {
                 var cmdHandler = handler as FtpCommandHandler;
                 var isAbortable = cmdHandler?.IsAbortable ?? false;
                 if (isAbortable)
                 {
                     var newBackgroundTask = Data.BackgroundCommandHandler.Execute(handler, handlerCommand);
                     if (newBackgroundTask != null)
                     {
                         _activeBackgroundTask = newBackgroundTask;
                         response = null;
                     }
                     else
                     {
                         response = new FtpResponse(503, "Parallel commands aren't allowed.");
                     }
                 }
                 else
                 {
                     response = await handler.Process(handlerCommand, _cancellationTokenSource.Token);
                 }
             }
             catch (Exception ex)
             {
                 Log?.Error(ex, "Failed to process message ({0})", command);
                 response = new FtpResponse(501, "Syntax error in parameters or arguments.");
             }
         }
     }
     else
     {
         response = new FtpResponse(500, "Syntax error, command unrecognized.");
     }
     if (response != null)
         await WriteAsync(response, _cancellationTokenSource.Token);
 }
Exemple #14
0
 /// <summary>
 /// Logs a info message with the data of the <see cref="FtpResponse"/>.
 /// </summary>
 /// <param name="log">The <see cref="ILogger"/> to use.</param>
 /// <param name="response">The <see cref="FtpResponse"/> to log.</param>
 public static void Info([NotNull] this ILogger log, [NotNull] FtpResponse response)
 {
     log.LogInformation("{code} {message}", response.Code, response.Message);
 }
 /// <summary>
 /// Logs an error message with the data of the <see cref="FtpResponse"/>
 /// </summary>
 /// <param name="log">The <see cref="IFtpLog"/> to use</param>
 /// <param name="response">The <see cref="FtpResponse"/> to log</param>
 public static void Error([NotNull] this IFtpLog log, [NotNull] FtpResponse response)
 {
     log.Error("{0}", response);
 }
 /// <summary>
 /// Logs a warning message with the data of the <see cref="FtpResponse"/>
 /// </summary>
 /// <param name="log">The <see cref="IFtpLog"/> to use</param>
 /// <param name="response">The <see cref="FtpResponse"/> to log</param>
 public static void Warn([NotNull] this IFtpLog log, [NotNull] FtpResponse response)
 {
     log.Warn("{0}", response);
 }
 /// <summary>
 /// Logs a info message with the data of the <see cref="FtpResponse"/>
 /// </summary>
 /// <param name="log">The <see cref="IFtpLog"/> to use</param>
 /// <param name="response">The <see cref="FtpResponse"/> to log</param>
 public static void Info([NotNull] this IFtpLog log, [NotNull] FtpResponse response)
 {
     log.Debug("{0}", response);
 }
 /// <summary>
 /// Logs a trace message with the data of the <see cref="FtpResponse"/>
 /// </summary>
 /// <param name="log">The <see cref="IFtpLog"/> to use</param>
 /// <param name="response">The <see cref="FtpResponse"/> to log</param>
 public static void Trace([NotNull] this IFtpLog log, [NotNull] FtpResponse response)
 {
     log.Trace("{0}", response);
 }
        public Task<FtpResponse> Execute([NotNull] FtpCommandHandlerBase handler, [NotNull] FtpCommand command)
        {
            Contract.Ensures(Contract.Result<Task<FtpResponse>>() != null);
            lock (_syncRoot)
            {
                if (_handlerTask != null)
                    return null;

                _cancellationTokenSource = new CancellationTokenSource();
                _handlerTask = handler.Process(command, _cancellationTokenSource.Token);
            }

            var taskCanceled = _handlerTask
                .ContinueWith(
                    t =>
                    {
                        var response = new FtpResponse(426, "Connection closed; transfer aborted.");
                        Debug.WriteLine($"Background task cancelled with response {response}");
                        return response;
                    },
                    TaskContinuationOptions.OnlyOnCanceled);

            var taskCompleted = _handlerTask
                .ContinueWith(
                    t =>
                    {
                        var response = t.Result;
                        Debug.WriteLine($"{DateTimeOffset.UtcNow} Background task finished successfully with response {response}");
                        return response;
                    },
                    TaskContinuationOptions.OnlyOnRanToCompletion);

            var taskFaulted = _handlerTask
                .ContinueWith(
                    t =>
                    {
                        var ex = t.Exception;
                        _connection.Log?.Error(ex, "Error while processing background command {0}", command);
                        var response = new FtpResponse(501, "Syntax error in parameters or arguments.");
                        Debug.WriteLine($"Background task failed with response {response}");
                        return response;
                    },
                    TaskContinuationOptions.OnlyOnFaulted);

            taskFaulted.ContinueWith(t => { }, TaskContinuationOptions.OnlyOnCanceled);
            taskCompleted.ContinueWith(t => { }, TaskContinuationOptions.OnlyOnCanceled);
            taskCanceled.ContinueWith(t => { }, TaskContinuationOptions.OnlyOnCanceled);

            return Task.Run(
                () =>
                {
                    var tasks = new List<Task<FtpResponse>> { taskCompleted, taskCanceled, taskFaulted };

                    do
                    {
                        try
                        {
                            var waitTasks = tasks.Where(x => !x.IsCompleted).Cast<Task>().ToArray();
                            if (waitTasks.Length != 0)
                            {
                                Debug.WriteLine($"Waiting for {waitTasks.Length} background tasks");
                                Task.WaitAll(waitTasks);
                            }
                        }
                        catch (AggregateException ex)
                        {
                            ex.Handle(e => e is TaskCanceledException);
                        }
                    }
                    while (tasks.Any(t => !t.IsCompleted));

                    var response = tasks.Single(x => x.Status == TaskStatus.RanToCompletion).Result;
                    Debug.WriteLine($"{DateTimeOffset.UtcNow} Background task finished with response {response}");

                    lock (_syncRoot)
                        _handlerTask = null;

                    return response;
                });
        }