private Task ProcessPendingLogEvents() { // get copy of pending log events quickly in a lock and clear list List <IPAddressLogEvent> events = null; lock (pendingLogEvents) { events = new List <IPAddressLogEvent>(pendingLogEvents); pendingLogEvents.Clear(); } List <IPAddressLogEvent> bannedIPs = new List <IPAddressLogEvent>(); object transaction = BeginTransaction(); try { // loop through events, for failed and successful logins, we want to group / aggregate the same event from the // same remote ip address foreach (IPAddressLogEvent evt in events) { if (!IPBanFirewallUtility.TryNormalizeIPAddress(evt.IPAddress, out string normalizedIPAddress)) { continue; } evt.IPAddress = normalizedIPAddress; switch (evt.Type) { case IPAddressEventType.FailedLogin: // if we are not already banned... if (!DB.TryGetIPAddressState(evt.IPAddress, out IPBanDB.IPAddressState? state, transaction) || state.Value == IPBanDB.IPAddressState.FailedLogin) { ProcessIPAddressEvent(evt, pendingFailedLogins, Config.MinimumTimeBetweenFailedLoginAttempts, "failed"); } break; case IPAddressEventType.SuccessfulLogin: ProcessIPAddressEvent(evt, pendingSuccessfulLogins, Config.MinimumTimeBetweenSuccessfulLoginAttempts, "successful"); break; case IPAddressEventType.Blocked: // if we are not already banned... if (!DB.TryGetIPAddressState(evt.IPAddress, out IPBanDB.IPAddressState? state2, transaction) || state2.Value == IPBanDB.IPAddressState.FailedLogin) { // make sure the ip address is ban pending AddBannedIPAddress(evt.IPAddress, evt.Source, evt.UserName, bannedIPs, evt.Timestamp, false, evt.Count, string.Empty, transaction, evt.External); } break; case IPAddressEventType.Unblocked: DB.SetIPAddressesState(new string[] { evt.IPAddress }, IPBanDB.IPAddressState.RemovePending, transaction); firewallNeedsBlockedIPAddressesUpdate = true; break; } } CommitTransaction(transaction); } catch (Exception ex) { RollbackTransaction(transaction); Logger.Error(ex); } ExecuteExternalProcessForBannedIPAddresses(bannedIPs); return(Task.CompletedTask); }
protected virtual async Task <bool> GetUrl(UrlType urlType) { if ((urlType == UrlType.Start && GotStartUrl) || string.IsNullOrWhiteSpace(LocalIPAddressString) || string.IsNullOrWhiteSpace(FQDN)) { return(false); } else if (urlType == UrlType.Stop) { GotStartUrl = false; } string url; switch (urlType) { case UrlType.Start: url = Config.GetUrlStart; break; case UrlType.Stop: url = Config.GetUrlStop; break; case UrlType.Update: url = Config.GetUrlUpdate; break; case UrlType.Config: url = Config.GetUrlConfig; break; default: return(false); } if (!string.IsNullOrWhiteSpace(url)) { url = ReplaceUrl(url); try { KeyValuePair <string, object>[] headers = (Authorization is null ? null : new KeyValuePair <string, object>[] { new KeyValuePair <string, object>("Authorization", Authorization) }); byte[] bytes = await RequestMaker.MakeRequestAsync(new Uri(url), headers : headers); if (urlType == UrlType.Start) { GotStartUrl = true; } else if (urlType == UrlType.Update) { // if the update url sends bytes, we assume a software update, and run the result as an .exe if (bytes.Length != 0) { string tempFile = Path.Combine(OSUtility.TempFolder, "IPBanServiceUpdate.exe"); File.WriteAllBytes(tempFile, bytes); // however you are doing the update, you must allow -c and -d parameters // pass -c to tell the update executable to delete itself when done // pass -d for a directory which tells the .exe where this service lives string args = "-c \"-d=" + AppContext.BaseDirectory + "\""; ProcessUtility.CreateDetachedProcess(tempFile, args); } } else if (urlType == UrlType.Config && bytes.Length != 0) { await WriteConfigAsync(Encoding.UTF8.GetString(bytes)); } } catch (Exception ex) { Logger.Error(ex, "Error getting url of type {0} at {1}", urlType, url); } } return(true); }
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()); } } }
private void LoadFirewall(IPBanConfig oldConfig) { IIPBanFirewall existing = Firewall; Firewall = FirewallCreator.CreateFirewall(Config, Firewall); if (existing != Firewall) { AddUpdater(Firewall); Logger.Warn("Loaded firewall type {0}", Firewall.GetType()); if (existing != null) { RemoveUpdater(existing); // transfer banned ip to new firewall Firewall.BlockIPAddresses(null, ipDB.EnumerateBannedIPAddresses()).Sync(); } } if (oldConfig is null) { // clear out all previous custom rules foreach (string rule in Firewall.GetRuleNames(Firewall.RulePrefix + "EXTRA_").ToArray()) { Firewall.DeleteRule(rule); } } else { // check for updated / new / removed block rules List <string> deleteList = new List <string>(oldConfig.ExtraRules.Select(r => r.Name)); // cleanup rules that are no longer in the config foreach (string newRule in Config.ExtraRules.Select(r => r.Name)) { deleteList.Remove(newRule); } foreach (string rule in deleteList) { foreach (string ruleName in Firewall.GetRuleNames(rule).ToArray()) { Firewall.DeleteRule(ruleName); } } } // ensure firewall is cleared out if needed - will only execute once UpdateBannedIPAddressesOnStart(); // ensure windows event viewer is setup if needed - will only execute once SetupWindowsEventViewer(); // add/update global rules Firewall.AllowIPAddresses("GlobalWhitelist", Config.Whitelist); Firewall.BlockIPAddresses("GlobalBlacklist", Config.BlackList); // add/update user specified rules foreach (IPBanFirewallRule rule in Config.ExtraRules) { if (rule.Block) { Firewall.BlockIPAddresses(rule.Name, rule.IPAddressRanges, rule.AllowPortRanges); } else { Firewall.AllowIPAddresses(rule.Name, rule.IPAddressRanges, rule.AllowPortRanges); } } }
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 { // see if there is an override for max failed login attempts maxFailedLoginAttempts = (failedLogin.FailedLoginThreshold > 0 ? failedLogin.FailedLoginThreshold : 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.Value == IPBanDB.IPAddressState.Active || state.Value == 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.External) { await IPBanDelegate.LoginAttemptFailed(ipAddress, source, userName, MachineGuid, OSName, OSVersion, UtcNow); } AddBannedIPAddress(ipAddress, source, userName, bannedIpAddresses, now, configBlacklisted, newCount, string.Empty, transaction, failedLogin.External); } } else { Logger.Debug("Failed login count {0} <= ban count {1}", newCount, maxFailedLoginAttempts); if (OSUtility.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.External) { await IPBanDelegate.LoginAttemptFailed(ipAddress, source, userName, MachineGuid, OSName, OSVersion, UtcNow); } } } } catch (Exception ex) { Logger.Error(ex); } } CommitTransaction(transaction); ExecuteExternalProcessForBannedIPAddresses(bannedIpAddresses); } catch (Exception ex) { RollbackTransaction(transaction); Logger.Error(ex); } }