using System; using System.Collections.Generic; using System.Net; using System.Threading; using System.Threading.Tasks; using EmbedIO.Security.Internal; namespace EmbedIO.Security { /// /// A module to ban clients by IP address, based on TCP requests-per-second or RegEx matches on log messages. /// /// public class IPBanningModule : WebModuleBase, IDisposable { /// /// The default ban minutes. /// public const int DefaultBanMinutes = 30; private const string NoConfigurationFound = "No configuration was found for the base route provided."; private bool _disposed; /// /// Initializes a new instance of the class. /// /// The base route. /// A collection of valid IPs that never will be banned. /// Minutes that an IP will remain banned. public IPBanningModule(string baseRoute = "/", IEnumerable? whitelist = null, int banMinutes = DefaultBanMinutes) : base(baseRoute) { Configuration = IPBanningExecutor.RetrieveInstance(baseRoute, banMinutes); AddToWhitelist(whitelist); } /// /// Finalizes an instance of the class. /// ~IPBanningModule() { Dispose(false); } /// public override bool IsFinalHandler => false; /// /// Gets the client address. /// /// /// The client address. /// public IPAddress? ClientAddress { get; private set; } internal IPBanningConfiguration Configuration { get; } /// /// Registers the criterion. /// /// The criterion. public void RegisterCriterion(IIPBanningCriterion criterion) => Configuration.RegisterCriterion(criterion); /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Gets the list of current banned IPs. /// /// The base route. /// /// A collection of in the blacklist. /// /// baseRoute public static IEnumerable GetBannedIPs(string baseRoute = "/") => IPBanningExecutor.TryGetInstance(baseRoute, out var instance) ? instance.BlackList : throw new ArgumentException(NoConfigurationFound, nameof(baseRoute)); /// /// Tries to ban an IP explicitly. /// /// The IP address to ban. /// Minutes that the IP will remain banned. /// The base route. /// true if the IP was explicitly banned. /// /// true if the IP was added to the blacklist; otherwise, false. /// public static bool TryBanIP(IPAddress address, int banMinutes, string baseRoute = "/", bool isExplicit = true) => TryBanIP(address, DateTime.Now.AddMinutes(banMinutes), baseRoute, isExplicit); /// /// Tries to ban an IP explicitly. /// /// The IP address to ban. /// A specifying the duration that the IP will remain banned. /// The base route. /// true if the IP was explicitly banned. /// /// true if the IP was added to the blacklist; otherwise, false. /// public static bool TryBanIP(IPAddress address, TimeSpan banDuration, string baseRoute = "/", bool isExplicit = true) => TryBanIP(address, DateTime.Now.Add(banDuration), baseRoute, isExplicit); /// /// Tries to ban an IP explicitly. /// /// The IP address to ban. /// A specifying the expiration time of the ban. /// The base route. /// true if the IP was explicitly banned. /// /// true if the IP was added to the blacklist; otherwise, false. /// /// baseRoute public static bool TryBanIP(IPAddress address, DateTime banUntil, string baseRoute = "/", bool isExplicit = true) { if (!IPBanningExecutor.TryGetInstance(baseRoute, out var instance)) throw new ArgumentException(NoConfigurationFound, nameof(baseRoute)); return instance.TryBanIP(address, isExplicit, banUntil); } /// /// Tries to unban an IP explicitly. /// /// The IP address. /// The base route. /// /// true if the IP was removed from the blacklist; otherwise, false. /// /// baseRoute public static bool TryUnbanIP(IPAddress address, string baseRoute = "/") => IPBanningExecutor.TryGetInstance(baseRoute, out var instance) ? instance.TryRemoveBlackList(address) : throw new ArgumentException(NoConfigurationFound, nameof(baseRoute)); internal void AddToWhitelist(IEnumerable? whitelist) => Configuration.AddToWhitelistAsync(whitelist).GetAwaiter().GetResult(); /// protected override void OnStart(CancellationToken cancellationToken) { Configuration.Lock(); base.OnStart(cancellationToken); } /// protected override Task OnRequestAsync(IHttpContext context) { ClientAddress = context.Request.RemoteEndPoint.Address; return Configuration.CheckClient(ClientAddress); } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { IPBanningExecutor.TryRemoveInstance(BaseRoute); Configuration.Dispose(); } _disposed = true; } } }