3using System.ComponentModel;
6using System.Net.NetworkInformation;
8using System.Security.Authentication;
9using System.Security.Cryptography.X509Certificates;
11using System.Threading.Tasks;
74 private LinkedList<TcpListener> listeners =
new LinkedList<TcpListener>();
76 private X509Certificate serverCertificate;
77 private Dictionary<int, KeyValuePair<ClientCertificates, bool>> portSpecificMTlsSettings;
79 private bool trustClientCertificates =
false;
80 private bool clientCertificateSettingsLocked =
false;
85 private readonly
bool hasDataPorts =
false;
86 private bool encryptionRequired;
87 private bool disposed =
false;
134 : this(
Domain, GetControlPort(Ports), GetEncryptedControlPort(Ports),
140 private static int GetControlPort(
int[] Ports)
142 if ((Ports?.Length ?? 0) == 0)
143 throw new ArgumentException(
"No FTP ports defined.", nameof(Ports));
145 foreach (
int Port
in Ports)
154 private static int? GetEncryptedControlPort(
int[] Ports)
156 if ((Ports?.Length ?? 0) == 0)
159 foreach (
int Port
in Ports)
168 private static int[] GetDataPorts(
int[] Ports)
170 List<int> Result =
new List<int>();
171 int ControlPort = GetControlPort(Ports);
173 foreach (
int Port
in Ports)
175 if (Port != ControlPort)
179 return Result.ToArray();
182 private class DataPortReference
186 public DataPortReference(
int Port)
222 : this(
Domain, new int[] { ControlPort }, EncryptedControlPort.HasValue ?
new int[] { EncryptedControlPort.Value } : Array.
Empty<
int>(),
262 Dictionary<
int, KeyValuePair<ClientCertificates, bool>> PortSpecificSettings,
bool LockSettings,
265 this.persistenceLayer = PersistenceLayer;
269 this.portSpecificMTlsSettings = PortSpecificSettings;
270 this.clientCertificateSettingsLocked = LockSettings;
275 throw new ArgumentException(
"Server Certificate must be provided, if encryption is required.", nameof(
ServerCertificate));
278 this.clientConnections.Removed += this.ClientConnections_Removed;
280 if ((DataPorts?.Length ?? 0) > 0)
282 this.hasDataPorts =
true;
285 foreach (
int Port
in DataPorts)
286 this.dataPorts.Queue(
new DataPortReference(Port));
291 foreach (NetworkInterface Interface
in NetworkInterface.GetAllNetworkInterfaces())
293 if (Interface.OperationalStatus != OperationalStatus.Up)
296 IPInterfaceProperties Properties = Interface.GetIPProperties();
298 foreach (UnicastIPAddressInformation UnicastAddress
in Properties.UnicastAddresses)
300 if ((UnicastAddress.Address.AddressFamily == AddressFamily.InterNetwork && Socket.OSSupportsIPv4) ||
301 (UnicastAddress.Address.AddressFamily == AddressFamily.InterNetworkV6 && Socket.OSSupportsIPv6))
303 foreach (
int Port
in ControlPorts)
305 this.OpenDataListener(this.AcceptTcpClientFtpControlCallback,
306 UnicastAddress.Address, Port,
false);
309 if (!(EncryptedControlPorts is
null))
311 foreach (
int Port
in EncryptedControlPorts)
315 this.OpenDataListener(this.AcceptTcpClientFtpsControlCallback,
316 UnicastAddress.Address, Port,
false);
342 if (this.dataPorts is
null)
345 DataPortReference Ref = await this.dataPorts.Wait(Timeout);
356 this.dataPorts?.Queue(
new DataPortReference(Port));
359 internal DataPort OpenDataListener(AsyncCallback Callback, IPAddress Address,
360 int Port,
bool ReleaseAfterUse)
365 this.externalSniffers.Information(
"Opening listening port on " + Address.ToString() +
".");
367 this.externalSniffers.Information(
"Opening listening port " + Port.ToString() +
" on " + Address.ToString() +
".");
369 DataPort Result =
new DataPort()
371 Listener =
new TcpListener(Address, Port),
372 ReleaseAfterUse = ReleaseAfterUse,
377 Result.Listener.BeginAcceptTcpClient(Callback, Result);
379 lock (this.listeners)
381 this.listeners.AddLast(Result.Listener);
384 if (Result.Listener.LocalEndpoint is IPEndPoint LocalEndpoint)
386 this.externalSniffers.Information(
"Port " + LocalEndpoint.Port.ToString() +
" on " + LocalEndpoint.Address.ToString() +
" opened.");
387 Result.Port = LocalEndpoint.Port;
388 Result.LocalEndpoint = LocalEndpoint;
392 this.externalSniffers.Information(
"Port on " + Address.ToString() +
" opened.");
405 internal void Remove(DataPort DataPort)
407 lock (this.listeners)
409 this.listeners.Remove(DataPort.Listener);
412 if (DataPort.ReleaseAfterUse)
451 await e.
Value.DisposeAsync();
464 get => this.clientConnections.
Count;
473 List<FtpClientConnection> Connections =
new List<FtpClientConnection>();
475 foreach (Guid Id
in this.clientConnections.
GetKeys())
478 Connections.Add(Connection);
481 Connections.Sort((c1, c2) =>
483 return c1.UserName.CompareTo(c2.UserName);
486 return Connections.ToArray();
497 return this.clientConnections.
TryGetValue(ID, out Connection);
524 get => this.encryptionRequired;
525 set => this.encryptionRequired = value;
547 Dictionary<
int, KeyValuePair<ClientCertificates, bool>> PortSpecificSettings,
bool LockSettings)
549 if (this.clientCertificateSettingsLocked)
550 throw new InvalidOperationException(
"Mutual TLS settings locked.");
554 this.portSpecificMTlsSettings = PortSpecificSettings;
555 this.clientCertificateSettingsLocked = LockSettings;
576 if (!(this.portSpecificMTlsSettings is
null) &&
577 this.portSpecificMTlsSettings.TryGetValue(Port, out KeyValuePair<ClientCertificates, bool> P))
596 this.disposed =
true;
598 if (!(this.clientConnections is
null))
600 this.clientConnections.
Clear();
601 this.clientConnections.
Dispose();
602 this.clientConnections =
null;
605 if (!(this.listeners is
null))
607 LinkedList<TcpListener> Listeners = this.listeners;
608 this.listeners =
null;
610 foreach (TcpListener Listener
in Listeners)
614 if (!(this.externalSniffers is
null))
616 foreach (
ISniffer Sniffer
in this.externalSniffers)
617 (Sniffer as IDisposable)?.
Dispose();
628 lock (this.listeners)
630 return this.GetOpenPorts(this.listeners);
635 private int[] GetOpenPorts(LinkedList<TcpListener> Listeners)
637 SortedDictionary<int, bool> Open =
new SortedDictionary<int, bool>();
639 if (!(Listeners is
null))
641 IPEndPoint IPEndPoint;
643 foreach (TcpListener Listener
in Listeners)
645 IPEndPoint = Listener.LocalEndpoint as IPEndPoint;
646 if (!(IPEndPoint is
null))
647 Open[IPEndPoint.Port] =
true;
651 int[] Result =
new int[Open.Count];
652 Open.Keys.CopyTo(Result, 0);
661 private void AcceptTcpClientFtpControlCallback(IAsyncResult ar)
663 this.AcceptTcpClientControlCallback(ar,
false);
666 private void AcceptTcpClientFtpsControlCallback(IAsyncResult ar)
668 this.AcceptTcpClientControlCallback(ar,
true);
671 private async
void AcceptTcpClientControlCallback(IAsyncResult ar,
bool Tls)
678 DataPort Port = (DataPort)ar.AsyncState;
679 TcpListener Listener = Port.Listener;
683 TcpClient Client = Listener.EndAcceptTcpClient(ar);
684 FtpClientControlConnection ClientConnection;
687 if (this.externalSniffers.HasSniffers)
688 Sniffers = this.externalSniffers.Sniffers;
703 ClientConnection =
new FtpClientControlConnection(
BinaryTcpClient,
this, this.persistenceLayer,
708 ClientConnection.OnStateChanged += this.Nop;
710 this.clientConnections[ClientConnection.ID] = ClientConnection;
715 await ClientConnection.BeginWrite(
"220 Service ready.\r\n",
null,
null);
723 Listener.BeginAcceptTcpClient(this.AcceptTcpClientFtpsControlCallback, Port);
725 Listener.BeginAcceptTcpClient(this.AcceptTcpClientFtpControlCallback, Port);
729 catch (SocketException)
733 catch (ObjectDisposedException)
737 catch (NullReferenceException)
743 if (this.listeners is
null)
751 bool TrustCertificates,
bool Control,
ISniffer[] Sniffers)
755 if (Security.LoginMonitor.LoginAuditor.CanStartTls(RemoteEndpoint))
759 if (this.externalSniffers.HasSniffers)
761 this.externalSniffers.Information(
"Switching to TLS. (Client Certificates: " +
ClientCertificates.ToString() +
762 ", Trust Certificates: " + TrustCertificates.ToString() +
")");
768 if (this.externalSniffers.HasSniffers)
770 StringBuilder sb =
new StringBuilder();
772 sb.Append(
"TLS established");
773 sb.Append(
". Cipher Strength: ");
775 sb.Append(
", Hash Strength: ");
777 sb.Append(
", Key Exchange Strength: ");
780 this.externalSniffers.Information(sb.ToString());
784 if (this.externalSniffers.HasSniffers)
788 sb.Append(
"Remote Certificate received. Valid: ");
790 sb.Append(
", Subject: ");
792 sb.Append(
", Issuer: ");
794 sb.Append(
", S/N: ");
796 sb.Append(
", Hash: ");
799 this.externalSniffers.Information(sb.ToString());
806 FtpClientControlConnection ClientConnection =
new FtpClientControlConnection(Client,
809 ClientConnection.Information(
"Encrypted Control Connection accepted from " + Client.
RemoteEndPoint +
".");
810 ClientConnection.OnStateChanged += this.Nop;
812 this.clientConnections[ClientConnection.ID] = ClientConnection;
814 await this.ClientConnectionAdded.Raise(
this,
new ClientConnectionEventArgs(ClientConnection));
818 await ClientConnection.BeginWrite(
"220 Service ready.\r\n",
null,
null);
823 catch (AuthenticationException ex)
825 if (this.externalSniffers.HasSniffers)
826 this.externalSniffers.Exception(ex);
828 await this.LoginFailure(ex, Client, RemoteEndpoint);
830 catch (SocketException ex)
832 if (this.externalSniffers.HasSniffers)
833 this.externalSniffers.Exception(ex);
837 catch (Win32Exception ex)
839 if (this.externalSniffers.HasSniffers)
840 this.externalSniffers.Exception(ex);
842 await this.LoginFailure(ex, Client, RemoteEndpoint);
844 catch (IOException ex)
846 if (this.externalSniffers.HasSniffers)
847 this.externalSniffers.Exception(ex);
853 if (this.externalSniffers.HasSniffers)
854 this.externalSniffers.Exception(ex);
864 internal async Task LoginFailure(Exception ex,
BinaryTcpClient Client,
string RemoteIpEndpoint)
867 Security.LoginMonitor.LoginAuditor.ReportTlsHackAttempt(RemoteIpEndpoint,
"TLS handshake failed: " + ex2.Message,
"HTTPS");
874 return Task.CompletedTask;
877 internal string GetTransformPath()
879 foreach (
ISniffer Sniffer
in this.externalSniffers.Sniffers)
888 internal void Closed(FtpClientConnection Connection)
890 this.clientConnections?.
Remove(Connection.ID);
893 internal void Ping(FtpClientConnection Connection)
895 this.clientConnections?.
ContainsKey(Connection.ID);
909 return this.persistenceLayer.GetAccount(UserName);
915 public event EventHandlerAsync<ClientConnectionEventArgs> ClientConnectionAdded =
null;
920 public event EventHandlerAsync<ClientConnectionEventArgs> ClientConnectionRemoved =
null;
Static class managing the application event log. Applications and services log events on this static ...
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Implements a binary TCP Client, by encapsulating a TcpClient. It also makes the use of TcpClient safe...
void Bind()
Binds to a TcpClient that was already connected when provided to the constructor.
bool RemoteCertificateValid
If the remote certificate is valid.
int HashStrength
Hash algorithm strength. (Nr bits of brute force complexity required to break algorithm).
string RemoteEndPoint
Remote End-point of connection. This corresponds to the IP Endpoint of the remote party in normal cas...
void Continue()
Continues reading from the socket, if paused in an event handler.
virtual Task DisposeAsync()
Disposes of the object asynchronously. The underlying TcpClient is either disposed directly,...
X509Certificate RemoteCertificate
Certificate used by the remote endpoint.
int KeyExchangeStrength
Key Exchange strength. (Nr bits of brute force complexity required to break algorithm).
int CipherStrength
Cipher strength. (Nr bits of brute force complexity required to break algorithm).
Task UpgradeToTlsAsServer(X509Certificate ServerCertificate)
Upgrades a server connection to TLS.
Simple base class for classes implementing communication protocols.
Client Connection event argument.
Abstract base class for FTP client connections.
Implements a simple FTP Server, as defined in:
int NrClientConnections
Number of client connections.
FtpServer(CaseInsensitiveString Domain, int ControlPort, X509Certificate ServerCertificate, bool EncryptionRequired, IFtpServerPersistenceLayer PersistenceLayer)
Implements an FTP server.
FtpServer(CaseInsensitiveString Domain, int ControlPort, int[] DataPorts, X509Certificate ServerCertificate, bool EncryptionRequired, IFtpServerPersistenceLayer PersistenceLayer)
Implements an FTP server.
const string FtpWritePrivilegePrefix
Prefix of FTP Write Privileges
bool AllowSafeDataChannel
If data can be transferred in the clear, with integrity protection
X509Certificate ServerCertificate
Server domain certificate.
CommunicationLayer ExternalSniffers
External Sniffers for FTP communication.
const int DefaultBufferSize
Default buffer size (16384).
void Dispose()
IDisposable.Dispose
ClientCertificates ClientCertificates
If client certificates are not used by default, optional or required.
EventHandlerAsync< ClientConnectionEventArgs > ClientConnectionAdded
Event raised when a client connection has been added.
FtpServer(CaseInsensitiveString Domain, int[] ControlPorts, int[] EncryptedControlPorts, int[] DataPorts, X509Certificate ServerCertificate, bool EncryptionRequired, IFtpServerPersistenceLayer PersistenceLayer)
Implements an FTP server.
FtpServer(CaseInsensitiveString Domain, int[] ControlPorts, int[] EncryptedControlPorts, int[] DataPorts, X509Certificate ServerCertificate, bool EncryptionRequired, ClientCertificates ClientCertificates, bool TrustClientCertificates, Dictionary< int, KeyValuePair< ClientCertificates, bool > > PortSpecificSettings, bool LockSettings, IFtpServerPersistenceLayer PersistenceLayer)
Implements an FTP server.
bool AllowConfidentialDataChannel
If data can be transferred encrypted.
FtpServer(CaseInsensitiveString Domain, X509Certificate ServerCertificate, bool EncryptionRequired, IFtpServerPersistenceLayer PersistenceLayer)
Implements an FTP server.
bool AllowPrivateDataChannel
If data can be transferred encrypted, with integrity protection.
bool TrustClientCertificates
If client certificates should be trusted by default, even if they do not validate.
FtpServer(CaseInsensitiveString Domain, int[] Ports, X509Certificate ServerCertificate, bool EncryptionRequired, IFtpServerPersistenceLayer PersistenceLayer)
Implements an FTP server.
const int DefaultFtpDataPort
Default FTP Data Port (20).
const int DefaultFtpsControlPort
Default FTPS Control Port (990).
bool TryGetClientConnection(Guid ID, out FtpClientConnection Connection)
Tries to get a client connection, given its ID
bool HasDataPorts
If FTP Server has data ports defined.
const int DefaultConnectionBacklog
Default Connection backlog (10).
bool Disposed
If the class is disposed.
void ConfigureMutualTls(ClientCertificates ClientCertificates, bool TrustClientCertificates, Dictionary< int, KeyValuePair< ClientCertificates, bool > > PortSpecificSettings, bool LockSettings)
Configures Mutual-TLS capabilities of the server. Affects all connections, all resources.
void ConfigureMutualTls(ClientCertificates ClientCertificates, bool TrustClientCertificates, bool LockSettings)
Configures Mutual-TLS capabilities of the server. Affects all connections, all resources.
bool AllowClearDataChannel
If data can be transferred in the clear
void UpdateCertificate(X509Certificate ServerCertificate)
Updates the server certificate
EventHandlerAsync< ClientConnectionEventArgs > ClientConnectionRemoved
Event raised when a client connection has been removed.
void ReleaseDataPort(int Port)
Releases a port back to the pool of available data ports.
CaseInsensitiveString Domain
Domain name.
const int DefaultFtpControlPort
Default FTP Control Port (21).
void GetMTlsSettings(int Port, out ClientCertificates ClientCertificates, out bool TrustClientCertificates)
Gets mTLS settings for a given port number.
int[] OpenPorts
Ports successfully opened.
const string FtpReadPrivilegePrefix
Prefix of FTP Read Privileges
FtpClientConnection[] GetClientConnections()
Gets an array of client connections.
async Task< int?> GetDataPort(int Timeout)
Gets a free data port for use in a passive-mode data connection.
FtpServer(CaseInsensitiveString Domain, int ControlPort, int? EncryptedControlPort, int[] DataPorts, X509Certificate ServerCertificate, bool EncryptionRequired, IFtpServerPersistenceLayer PersistenceLayer)
Implements an FTP server.
bool EncryptionRequired
If C2S encryption is requried.
Module that controls the life cycle of communication.
static bool Stopping
If the system is stopping.
Outputs sniffed data to an XML file.
string Transform
Transform to use.
Represents a case-insensitive string.
static readonly CaseInsensitiveString Empty
Empty case-insensitive string
Implements an in-memory cache.
bool ContainsKey(KeyType Key)
Checks if a key is available in the cache.
void Dispose()
IDisposable.Dispose
int Count
Number of items in cache
bool Remove(KeyType Key)
Removes an item from the cache.
bool TryGetValue(KeyType Key, out ValueType Value)
Tries to get a value from the cache.
KeyType[] GetKeys()
Gets all available keys in the cache.
void Clear()
Clears the cache.
Event arguments for cache item removal events.
ValueType Value
Value of item that was removed.
Asynchronous First-in-First-out (FIFO) Queue, for use when transporting items of type T between task...
Helper methods for encrypting and decrypting streams of data.
const SslProtocols SecureTls
TLS 1.2 & 1.3
Persistence layer for FTP servers.
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Interface for Mutual TLS (mTLS) Clients or TLS servers.
FtpControlConnectionState
State of FTP connection.
ClientCertificates
Client Certificate Options