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); } }
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); } }
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; })); }
/// <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); }
/// <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); }
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); }
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); }
/// <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; }); }