/// <summary> /// Stop the service, dispose of all resources /// </summary> public void Dispose() { if (!IsRunning) { return; } try { cycleLock.WaitAsync().Sync(); IsRunning = false; GetUrl(UrlType.Stop).Sync(); cycleTimer?.Dispose(); IPBanDelegate?.Dispose(); IPBanDelegate = null; lock (updaters) { foreach (IUpdater updater in updaters.ToArray()) { updater.Dispose(); } updaters.Clear(); } ipDB?.Dispose(); Logger.Warn("Stopped IPBan service"); } finally { stopEvent.Release(); } }
/// <summary> /// Stop the service, dispose of all resources /// </summary> public void Dispose() { if (!IsRunning) { return; } IsRunning = false; try { serviceCancelTokenSource.Cancel(); GetUrl(UrlType.Stop).Sync(); cycleTimer?.Dispose(); IPBanDelegate?.Dispose(); IPBanDelegate = null; lock (updaters) { foreach (IUpdater updater in updaters.ToArray()) { updater.Dispose(); } updaters.Clear(); } foreach (LogFileScanner file in logFilesToParse) { file.Dispose(); } ipDB?.Dispose(); Logger.Warn("Stopped IPBan service"); } finally { stopEvent.Release(); } }
private async Task UpdateExpiredIPAddressStates() { HashSet <string> unbanIPAddressesToNotifyDelegate = (IPBanDelegate is null ? null : new HashSet <string>()); DateTime now = UtcNow; DateTime failLoginCutOff = (now - Config.ExpireTime); DateTime banCutOff = now; object transaction = DB.BeginTransaction(); try { HandleWhitelistChanged(transaction, unbanIPAddressesToNotifyDelegate); HandleExpiredLoginsAndBans(failLoginCutOff, banCutOff, transaction, unbanIPAddressesToNotifyDelegate); // notify delegate of all unbanned ip addresses if (IPBanDelegate != null) { foreach (string ip in unbanIPAddressesToNotifyDelegate) { await IPBanDelegate.IPAddressBanned(ip, null, null, MachineGuid, OSName, OSVersion, UtcNow, false); } } } catch (Exception ex) { Logger.Error(ex); DB.RollbackTransaction(transaction); } finally { DB.CommitTransaction(transaction); } }
private Task ProcessPendingSuccessfulLogins(IEnumerable <IPAddressLogEvent> ipAddresses) { foreach (IPAddressLogEvent info in ipAddresses) { Logger.Warn("Login succeeded, address: {0}, user name: {1}, source: {2}", info.IPAddress, info.UserName, info.Source); if (Config.ClearFailedLoginsOnSuccessfulLogin) { DB.DeleteIPAddress(info.IPAddress); firewallNeedsBlockedIPAddressesUpdate = true; } } if (IPBanDelegate != null) { return(Task.Run(() => { try { foreach (IPAddressLogEvent info in ipAddresses) { // pass the success login on IPBanDelegate.LoginAttemptSucceeded(info.IPAddress, info.Source, info.UserName, MachineGuid, OSName, OSVersion, info.Timestamp); } } catch (Exception ex) { Logger.Error(ex); } })); } return(Task.CompletedTask); }
/// <summary> /// Stop the service, dispose of all resources /// </summary> public void Dispose() { if (!IsRunning) { return; } IsRunning = false; try { firewallQueueCancel.Cancel(); GetUrl(UrlType.Stop).Sync(); cycleTimer?.Dispose(); IPBanDelegate?.Dispose(); IPBanDelegate = null; lock (updaters) { foreach (IUpdater updater in updaters.ToArray()) { updater.Dispose(); } updaters.Clear(); } foreach (IPBanLogFileScanner file in logFilesToParse) { file.Dispose(); } ipDB?.Dispose(); IPBanLog.Warn("Stopped IPBan service"); } catch { } stopEvent.Set(); }
/// <summary> /// Initialize and start the service /// </summary> /// <param name="cancelToken">Cancel token</param> public async Task RunAsync(CancellationToken cancelToken) { CancelToken = cancelToken; if (!IsRunning) { try { IsRunning = true; // set version AssemblyVersion = IPBanService.IPBanAssembly.GetName().Version.ToString(); // create db ipDB = new IPBanDB(DatabasePath ?? "ipban.sqlite"); // add some services AddUpdater(new IPBanUnblockIPAddressesUpdater(this, Path.Combine(AppContext.BaseDirectory, "unban.txt"))); AddUpdater(new IPBanBlockIPAddressesUpdater(this, Path.Combine(AppContext.BaseDirectory, "ban.txt"))); AddUpdater(DnsList); // start delegate if we have one IPBanDelegate?.Start(this); Logger.Warn("IPBan service started and initialized. Operating System: {0}", OSUtility.Instance.OSString()); Logger.WriteLogLevels(); // setup cycle timer if needed if (!ManualCycle) { // create a new timer that goes off in 1 second, this will change as the config is // loaded and the cycle time becomes whatever is in the config cycleTimer = new Timer(async(_state) => { try { await CycleTimerElapsed(); } catch { } }, null, 1000, Timeout.Infinite); } if (!ManualCycle) { await Task.Delay(Timeout.Infinite, cancelToken); } } catch (Exception ex) { if (!(ex is OperationCanceledException)) { Logger.Error($"Error in {nameof(IPBanService)}.{nameof(IPBanService.RunAsync)}", ex); } } } }
/// <summary> /// Initialize and start the service /// </summary> public async Task StartAsync() { if (IsRunning) { return; } try { IsRunning = true; ipDB = new IPBanDB(DatabasePath ?? "ipban.sqlite"); AddWindowsEventViewer(); AddUpdater(new IPBanUnblockIPAddressesUpdater(this, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "unban.txt"))); AddUpdater(new IPBanBlockIPAddressesUpdater(this, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ban.txt"))); AssemblyVersion = IPBanService.IPBanAssembly.GetName().Version.ToString(); await ReadAppSettings(); UpdateBannedIPAddressesOnStart(); IPBanDelegate?.Start(this); if (!ManualCycle) { if (RunFirstCycleRightAway) { await RunCycle(); // run one cycle right away } cycleTimer = new System.Timers.Timer(Config.CycleTime.TotalMilliseconds); cycleTimer.Elapsed += async(sender, e) => await CycleTimerElapsed(sender, e); cycleTimer.Start(); } Logger.Warn("IPBan {0} service started and initialized. Operating System: {1}", OSUtility.Name, OSUtility.OSString()); Logger.WriteLogLevels(); } catch (Exception ex) { Logger.Error("Critical error in IPBanService.Start", ex); } }
/// <summary> /// Check if an ip is whitelisted /// </summary> /// <param name="ipAddress">IP address</param> /// <returns>True if whitelisted, false otherwise</returns> public bool IsWhitelisted(string ipAddress) { return(Config.IsWhitelisted(ipAddress) || (IPBanDelegate != null && IPBanDelegate.IsIPAddressWhitelisted(ipAddress))); }
private async Task ProcessPendingFailedLogins(IReadOnlyList <IPAddressLogEvent> ipAddresses) { List <IPAddressLogEvent> bannedIpAddresses = new List <IPAddressLogEvent>(); object transaction = BeginTransaction(); try { foreach (IPAddressLogEvent failedLogin in ipAddresses) { try { string ipAddress = failedLogin.IPAddress; string userName = failedLogin.UserName; string source = failedLogin.Source; if (IsWhitelisted(ipAddress)) { Logger.Warn("Login failure, ignoring whitelisted ip address {0}, {1}, {2}", ipAddress, userName, source); } else { int maxFailedLoginAttempts; if (Config.IsWhitelisted(userName)) { maxFailedLoginAttempts = Config.FailedLoginAttemptsBeforeBanUserNameWhitelist; } else { maxFailedLoginAttempts = Config.FailedLoginAttemptsBeforeBan; } DateTime now = failedLogin.Timestamp; // check for the target user name for additional blacklisting checks bool ipBlacklisted = Config.IsBlackListed(ipAddress); bool userBlacklisted = (ipBlacklisted ? false : Config.IsBlackListed(userName)); bool userFailsWhitelistRegex = (userBlacklisted ? false : Config.UserNameFailsUserNameWhitelistRegex(userName)); bool editDistanceBlacklisted = (ipBlacklisted || userBlacklisted || userFailsWhitelistRegex ? false : !Config.IsUserNameWithinMaximumEditDistanceOfUserNameWhitelist(userName)); bool configBlacklisted = ipBlacklisted || userBlacklisted || userFailsWhitelistRegex || editDistanceBlacklisted; // if the event came in with a count of 0 that means it is an automatic ban int incrementCount = (failedLogin.Count < 1 ? maxFailedLoginAttempts : failedLogin.Count); int newCount = ipDB.IncrementFailedLoginCount(ipAddress, userName, source, UtcNow, incrementCount, transaction); Logger.Warn(now, "Login failure: {0}, {1}, {2}, {3}", ipAddress, userName, source, newCount); // if the ip address is black listed or the ip address has reached the maximum failed login attempts before ban, ban the ip address if (configBlacklisted || newCount >= maxFailedLoginAttempts) { Logger.Info("IP blacklisted: {0}, user name blacklisted: {1}, fails user name white list regex: {2}, user name edit distance blacklisted: {3}", ipBlacklisted, userBlacklisted, userFailsWhitelistRegex, editDistanceBlacklisted); if (ipDB.TryGetIPAddressState(ipAddress, out IPBanDB.IPAddressState state, transaction) && (state == IPBanDB.IPAddressState.Active || state == IPBanDB.IPAddressState.AddPending)) { Logger.Warn(now, "IP {0}, {1}, {2} ban pending.", ipAddress, userName, source); } else { Logger.Debug("Failed login count {0} >= ban count {1}{2}", newCount, maxFailedLoginAttempts, (configBlacklisted ? " config blacklisted" : string.Empty)); // if delegate and non-zero count, forward on - count of 0 means it was from external source, like a delegate if (IPBanDelegate != null && failedLogin.Count > 0) { await IPBanDelegate.LoginAttemptFailed(ipAddress, source, userName, MachineGuid, OSName, OSVersion, UtcNow); } AddBannedIPAddress(ipAddress, source, userName, bannedIpAddresses, now, configBlacklisted, newCount, string.Empty, transaction); } } else { Logger.Debug("Failed login count {0} <= ban count {1}", newCount, maxFailedLoginAttempts); if (OSUtility.Instance.UserIsActive(userName)) { Logger.Warn("Login failed for known active user {0}", userName); } // if delegate and non-zero count, forward on - count of 0 means it was from external source, like a delegate if (IPBanDelegate != null && failedLogin.Count > 0) { await IPBanDelegate.LoginAttemptFailed(ipAddress, source, userName, MachineGuid, OSName, OSVersion, UtcNow); } } } } catch (Exception ex) { Logger.Error(ex); } }
private void AddBannedIPAddress(string ipAddress, string source, string userName, List <IPAddressLogEvent> bannedIpAddresses, DateTime startBanDate, bool configBlacklisted, int counter, string extraInfo, object transaction, bool external) { // never ban whitelisted ip addresses if (IsWhitelisted(ipAddress)) { Logger.Info("Ignoring ban request for whitelisted ip address {0}", ipAddress); return; } TimeSpan[] banTimes = Config.BanTimes; TimeSpan banTime = banTimes.First(); DateTime banEndDate = startBanDate + banTime; // if we have an ip in the database, use the ban time to move to the next ban slot in the list of ban times // if ban times only has one entry, do not do this if (banTimes.Length > 1 && ipDB.TryGetIPAddress(ipAddress, out IPBanDB.IPAddressEntry ipEntry, transaction) && ipEntry.BanStartDate != null && ipEntry.BanEndDate != null) { // find the next ban time in the array banTime = ipEntry.BanEndDate.Value - ipEntry.BanStartDate.Value; for (int i = 0; i < banTimes.Length; i++) { if (banTime < banTimes[i]) { // ban for next timespan banTime = banTimes[i]; banEndDate = startBanDate + banTime; Logger.Info("Moving to next ban duration {0} at index {1} for ip {1}", banTimes[i], i, ipAddress); break; } } } int adjustedCount = (counter <= 0 ? Config.FailedLoginAttemptsBeforeBan : counter); bannedIpAddresses?.Add(new IPAddressLogEvent(ipAddress, userName, source, adjustedCount, IPAddressEventType.Blocked)); if (ipDB.SetBanDates(ipAddress, startBanDate, banEndDate, UtcNow, transaction)) { firewallNeedsBlockedIPAddressesUpdate = true; } Logger.Warn(startBanDate, "Banning ip address: {0}, user name: {1}, config black listed: {2}, count: {3}, extra info: {4}, duration: {5}", ipAddress, userName, configBlacklisted, counter, extraInfo, banTime); // if this is a delegate callback (counter of 0), exit out - we don't want to run handlers or processes for shared banned ip addresses if (counter <= 0) { return; } else if (BannedIPAddressHandler != null && System.Net.IPAddress.TryParse(ipAddress, out System.Net.IPAddress ipAddressObj) && !ipAddressObj.IsInternal()) { try { ExecuteTask(BannedIPAddressHandler.HandleBannedIPAddress(ipAddress, source, userName, OSName, OSVersion, AssemblyVersion, RequestMaker)); } catch { // eat exception, delicious } } if (IPBanDelegate != null && !external) { try { ExecuteTask(IPBanDelegate.IPAddressBanned(ipAddress, source, userName, MachineGuid, OSName, OSVersion, UtcNow, true)); } catch (Exception ex) { Logger.Info("Error calling ipban delegate with banned ip address: " + ex.ToString()); } } }