public virtual Task <bool> AllowIPAddresses(IEnumerable <string> ipAddresses, CancellationToken cancelToken = default) { try { return(Task.FromResult(UpdateRule(AllowRuleName, "ACCEPT", ipAddresses, hashTypeSingleIP, allowRuleMaxCount, null, cancelToken))); } catch (Exception ex) { IPBanLog.Error(ex); return(Task.FromResult(false)); } }
/// <summary> /// Get a value from configuration manager app settings /// </summary> /// <typeparam name="T">Type of value to get</typeparam> /// <param name="key">Key</param> /// <param name="defaultValue">Default value if null or not found</param> /// <returns>Value</returns> public T GetConfig <T>(string key, T defaultValue = default) { try { var converter = TypeDescriptor.GetConverter(typeof(T)); return((T)converter.ConvertFromInvariantString(appSettings[key])); } catch (Exception ex) { IPBanLog.Error(ex, "Error deserializing appSettings key {0}", key); return(defaultValue); } }
public virtual Task <bool> AllowIPAddresses(string ruleNamePrefix, IEnumerable <IPAddressRange> ipAddresses, IEnumerable <PortRange> allowedPorts = null, CancellationToken cancelToken = default) { try { ruleNamePrefix.ThrowIfNullOrEmpty(); return(Task.FromResult(UpdateRule(RulePrefix + ruleNamePrefix, "ACCEPT", ipAddresses.Select(r => r.ToCidrString()), hashTypeCidrMask, blockRuleMaxCount, allowedPorts, cancelToken))); } catch (Exception ex) { IPBanLog.Error(ex); return(Task.FromResult(false)); } }
public virtual Task <bool> BlockIPAddressesDelta(string ruleNamePrefix, IEnumerable <IPBanFirewallIPAddressDelta> deltas, IEnumerable <PortRange> allowedPorts = null, CancellationToken cancelToken = default) { try { string ruleName = (string.IsNullOrWhiteSpace(ruleNamePrefix) ? BlockRuleName : RulePrefix + ruleNamePrefix); return(Task.FromResult(UpdateRuleDelta(ruleName, "DROP", deltas, hashTypeSingleIP, blockRuleMaxCount, false, allowedPorts, cancelToken))); } catch (Exception ex) { IPBanLog.Error(ex); return(Task.FromResult(false)); } }
/// <summary> /// Easy way to execute processes. If the process has not finished after timeoutMilliseconds, it is forced killed. /// </summary> /// <param name="timeoutMilliseconds">Timeout in milliseconds</param> /// <param name="program">Program to run</param> /// <param name="args">Arguments</param> /// <param name="allowedExitCodes">Allowed exit codes, if null or empty it is not checked, otherwise a mismatch will throw an exception.</param> /// <returns>Output</returns> /// <exception cref="ApplicationException">Exit code did not match allowed exit codes</exception> public static string StartProcessAndWait(int timeoutMilliseconds, string program, string args, params int[] allowedExitCodes) { IPBanLog.Info($"Executing process {program} {args}..."); var process = new Process { StartInfo = new ProcessStartInfo(program, args) { CreateNoWindow = true, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden, RedirectStandardOutput = true, RedirectStandardError = true, Verb = processVerb } }; StringBuilder output = new StringBuilder(); process.OutputDataReceived += (object sender, DataReceivedEventArgs e) => { lock (output) { output.Append("[OUT]: "); output.AppendLine(e.Data); } }; process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) => { lock (output) { output.Append("[ERR]: "); output.AppendLine(e.Data); } }; process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); if (!process.WaitForExit(timeoutMilliseconds)) { lock (output) { output.Append("[ERR]: Terminating process due to 60 second timeout"); } process.Kill(); } if (allowedExitCodes.Length != 0 && Array.IndexOf(allowedExitCodes, process.ExitCode) < 0) { throw new ApplicationException($"Program {program} {args}: failed with exit code {process.ExitCode}, output: {output}"); } return(output.ToString()); }
public static async Task MainService(string[] args, Func <string[], Task> start, Action stop, bool requireAdministrator = true) { try { using (IPBanServiceRunner runner = new IPBanServiceRunner(args, start, stop)) { await runner.RunAsync(requireAdministrator); } } catch (Exception ex) { IPBanExtensionMethods.FileWriteAllTextWithRetry(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "startup_fail.txt"), ex.ToString()); IPBanLog.Fatal("Fatal error starting service", ex); } }
/// <summary> /// Set a field / variable from configuration manager app settings. If null or not found, nothing is changed. /// </summary> /// <typeparam name="T">Type of value to set</typeparam> /// <param name="key">Key</param> /// <param name="value">Value</param> public void GetConfig <T>(string key, ref T value) { try { var converter = TypeDescriptor.GetConverter(typeof(T)); if (appSettings.ContainsKey(key)) { value = (T)converter.ConvertFromInvariantString(appSettings[key]); } } catch (Exception ex) { IPBanLog.Error(ex, "Error deserializing appSettings key {0}", key); } }
protected override void OnStart(string[] args) { base.OnStart(args); Task.Run(async() => { try { await runner.start.Invoke(args); } catch (Exception ex) { IPBanLog.Error(ex); } }); }
/// <summary> /// Process a line, checking for ip addresses /// </summary> /// <param name="line">Line to process</param> /// <returns>True</returns> protected override bool OnProcessLine(string line) { IPBanLog.Debug("Parsing log file line {0}...", line); bool result = ParseRegex(regexFailure, line, false); if (!result) { result = ParseRegex(regexSuccess, line, true); if (!result) { IPBanLog.Debug("No match for line {0}", line); } } return(true); }
/// <summary> /// Update - if the text file path exists, all ip addresses from each line will be unbanned /// </summary> public async Task Update() { try { if (File.Exists(textFilePath)) { string[] lines = (await File.ReadAllLinesAsync(textFilePath)).Where(l => IPAddress.TryParse(l, out _)).ToArray(); IPBanLog.Warn("Queueing {0} ip addresses to unban from {1} file", lines.Length, textFilePath); UnblockIPAddresses(lines); IPBanExtensionMethods.FileDeleteWithRetry(textFilePath); } } catch (Exception ex) { IPBanLog.Error(ex); } }
protected override void OnInitialize() { base.OnInitialize(); IPBanLog.Info("Initializing IPBan database at {0}", ConnectionString); ExecuteNonQuery("PRAGMA auto_vacuum = INCREMENTAL;"); ExecuteNonQuery("PRAGMA journal_mode = WAL;"); ExecuteNonQuery("CREATE TABLE IF NOT EXISTS IPAddresses (IPAddress VARBINARY(16) NOT NULL, IPAddressText VARCHAR(64) NOT NULL, LastFailedLogin BIGINT NOT NULL, FailedLoginCount BIGINT NOT NULL, BanDate BIGINT NULL, PRIMARY KEY (IPAddress))"); ExecuteNonQueryIgnoreExceptions("ALTER TABLE IPAddresses ADD COLUMN State INT NOT NULL DEFAULT 0"); ExecuteNonQueryIgnoreExceptions("ALTER TABLE IPAddresses ADD COLUMN BanEndDate BIGINT NULL"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS IPAddresses_LastFailedLoginDate ON IPAddresses (LastFailedLogin)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS IPAddresses_BanDate ON IPAddresses (BanDate)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS IPAddresses_BanEndDate ON IPAddresses (BanEndDate)"); ExecuteNonQuery("CREATE INDEX IF NOT EXISTS IPAddresses_State ON IPAddresses (State)"); // set to failed login state if no ban date ExecuteNonQuery("UPDATE IPAddresses SET State = 3 WHERE State IN (0, 1) AND BanDate IS NULL"); }
/// <summary> /// Get a value from configuration manager app settings /// </summary> /// <typeparam name="T">Type of value to get</typeparam> /// <param name="key">Key</param> /// <param name="value">Value to set</param> /// <param name="minValue">Min value</param> /// <param name="maxValue">Max value</param> /// <param name="clampSmallTimeSpan">Whether to clamp small timespan to max value</param> /// <returns>Value</returns> public void GetConfig <T>(string key, ref T value, T?minValue = null, T?maxValue = null, bool clampSmallTimeSpan = true) where T : struct, IComparable <T> { try { var converter = TypeDescriptor.GetConverter(typeof(T)); value = (T)converter.ConvertFromInvariantString(appSettings[key]); } catch (Exception ex) { IPBanLog.Error(ex, "Error deserializing appSettings key {0}", key); } if (minValue != null && maxValue != null) { value = value.Clamp(minValue.Value, maxValue.Value, clampSmallTimeSpan); } }
private bool ParseRegex(Regex regex, string line, bool notifyOnly) { if (regex != null) { IPAddressLogEvent info = IPBanService.GetIPAddressInfoFromRegex(dns, regex, line); if (info.FoundMatch) { info.Type = (notifyOnly ? IPAddressEventType.SuccessfulLogin : IPAddressEventType.FailedLogin); info.Source = info.Source ?? Source; IPBanLog.Debug("Log file found match, ip: {0}, user: {1}, source: {2}, count: {3}, type: {4}", info.IPAddress, info.UserName, info.Source, info.Count, info.Type); loginHandler.AddIPAddressLogEvents(new IPAddressLogEvent[] { info }); return(true); } } return(false); }
private async Task FirewallTask(AsyncQueue <Func <CancellationToken, Task> > queue) { while (!firewallQueueCancel.IsCancellationRequested) { KeyValuePair <bool, Func <CancellationToken, Task> > nextAction = await queue.TryDequeueAsync(firewallQueueCancel.Token); if (nextAction.Key && nextAction.Value != null) { try { await nextAction.Value.Invoke(firewallQueueCancel.Token); } catch (Exception ex) { IPBanLog.Error(ex); } } } }
/// <summary> /// Process event viewer XML /// </summary> /// <param name="xml">XML</param> /// <returns>Log event or null if fail to parse/process</returns> public IPAddressLogEvent ProcessEventViewerXml(string xml) { IPBanLog.Debug("Processing event viewer xml: {0}", xml); XmlDocument doc = ParseXml(xml); IPAddressLogEvent info = ExtractEventViewerXml(doc); if (info != null && info.FoundMatch && (info.Type == IPAddressEventType.FailedLogin || info.Type == IPAddressEventType.SuccessfulLogin)) { if (!FindSourceAndUserNameForInfo(info, doc)) { // bad ip address return(null); } service.AddIPAddressLogEvents(new IPAddressLogEvent[] { info }); IPBanLog.Debug("Event viewer found: {0}, {1}, {2}, {3}", info.IPAddress, info.Source, info.UserName, info.Type); } return(info); }
public bool IsIPAddressBlocked(string ipAddress, out string ruleName, int port = -1) { ruleName = null; try { lock (policy) { for (int i = 0; ; i += MaxIpAddressesPerRule) { string firewallRuleName = BlockRulePrefix + i.ToString(CultureInfo.InvariantCulture); try { INetFwRule rule = policy.Rules.Item(firewallRuleName); if (rule is null) { // no more rules to check break; } else { HashSet <string> set = new HashSet <string>(rule.RemoteAddresses.Split(',').Select(i2 => IPAddressRange.Parse(i2).Begin.ToString())); if (set.Contains(ipAddress)) { ruleName = firewallRuleName; return(true); } } } catch { // no more rules to check break; } } } } catch (Exception ex) { IPBanLog.Error(ex); } return(false); }
private static void LoadVersionFromWmiApi() { try { // WMI API sometimes fails to initialize on .NET core on some systems, not sure why... // fall-back to WMI, maybe future .NET core versions will fix the bug using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Caption, Version FROM Win32_OperatingSystem")) { foreach (var result in searcher.Get()) { FriendlyName = result["Caption"] as string; Version = result["Version"] as string; break; } } } catch (Exception ex) { IPBanLog.Error(ex, "Unable to load os version from wmi api"); } }
/// <summary> /// Get a firewall ip address, clean and normalize /// </summary> /// <param name="ipAddress">IP Address</param> /// <param name="normalizedIP">The normalized ip ready to go in the firewall or null if invalid ip address</param> /// <returns>True if ip address can go in the firewall, false otherwise</returns> public static bool TryNormalizeIPAddress(this string ipAddress, out string normalizedIP) { normalizedIP = (ipAddress ?? string.Empty).Trim(); if (string.IsNullOrWhiteSpace(normalizedIP) || normalizedIP == "-" || normalizedIP == "0.0.0.0" || normalizedIP == "127.0.0.1" || normalizedIP == "::0" || normalizedIP == "::1" || !IPAddressRange.TryParse(normalizedIP, out IPAddressRange range)) { // try parsing assuming the ip is followed by a port int pos = normalizedIP.LastIndexOf(':'); if (pos >= 0) { normalizedIP = normalizedIP.Substring(0, pos); if (!IPAddressRange.TryParse(normalizedIP, out range)) { normalizedIP = null; return(false); } } else { normalizedIP = null; return(false); } } try { normalizedIP = (range.Begin.Equals(range.End) ? range.Begin.ToString() : range.ToCidrString()); } catch (Exception ex) { IPBanLog.Debug("Failed to normalize ip {0}, it is not a single ip or cidr range: {1}", ipAddress, ex); return(false); } return(true); }
protected int RunProcess(string program, bool requireExitCode, out IReadOnlyList <string> lines, string commandLine, params object[] args) { commandLine = string.Format(commandLine, args); string bash = "-c \"" + program + " " + commandLine.Replace("\"", "\\\"") + "\""; IPBanLog.Debug("Running firewall process: {0} {1}", program, commandLine); using (Process p = new Process { StartInfo = new ProcessStartInfo { FileName = "/bin/bash", Arguments = bash, UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true } }) { p.Start(); List <string> lineList = new List <string>(); string line; while ((line = p.StandardOutput.ReadLine()) != null) { lineList.Add(line); } lines = lineList; if (!p.WaitForExit(60000)) { IPBanLog.Error("Process {0} {1} timed out", program, commandLine); p.Kill(); } if (requireExitCode && p.ExitCode != 0) { IPBanLog.Error("Process {0} {1} had exit code {2}", program, commandLine, p.ExitCode); } return(p.ExitCode); } }
/// <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(); } IPBanLog.Warn("IPBan {0} service started and initialized. Operating System: {1}", IPBanOS.Name, IPBanOS.OSString()); IPBanLog.WriteLogLevels(); } catch (Exception ex) { IPBanLog.Error("Critical error in IPBanService.Start", ex); } }
public IPBanWindowsServiceRunner(IPBanServiceRunner runner, string[] args) { runner.ThrowIfNull(); try { IPBanLog.Warn("Running as a Windows service"); this.runner = runner; CanShutdown = false; CanStop = CanHandleSessionChangeEvent = CanHandlePowerEvent = true; var acceptedCommandsField = typeof(ServiceBase).GetField("acceptedCommands", BindingFlags.Instance | BindingFlags.NonPublic); if (acceptedCommandsField != null) { int acceptedCommands = (int)acceptedCommandsField.GetValue(this); acceptedCommands |= 0x00000100; // SERVICE_ACCEPT_PRESHUTDOWN; acceptedCommandsField.SetValue(this, acceptedCommands); } Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); } catch (Exception ex) { IPBanLog.Error(ex); } }
private void UpdateLogFiles(IPBanConfig newConfig) { // remove existing log files that are no longer in config foreach (IPBanLogFileScanner file in logFilesToParse.ToArray()) { if (newConfig.LogFilesToParse.FirstOrDefault(f => f.PathsAndMasks.Contains(file.PathAndMask)) is null) { file.Dispose(); logFilesToParse.Remove(file); } } foreach (IPBanLogFileToParse newFile in newConfig.LogFilesToParse) { string[] pathsAndMasks = newFile.PathAndMask.Split('\n'); for (int i = 0; i < pathsAndMasks.Length; i++) { string pathAndMask = pathsAndMasks[i].Trim(); if (pathAndMask.Length != 0) { // if we don't have this log file and the platform matches, add it if (logFilesToParse.FirstOrDefault(f => f.PathAndMask == pathAndMask) is null && !string.IsNullOrWhiteSpace(newFile.PlatformRegex) && Regex.IsMatch(IPBanOS.Description, newFile.PlatformRegex.ToString().Trim(), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)) { // log files use a timer internally and do not need to be updated regularly IPBanLogFileScanner scanner = new IPBanIPAddressLogFileScanner(this, DnsLookup, newFile.Source, pathAndMask, newFile.Recursive, newFile.FailedLoginRegex, newFile.SuccessfulLoginRegex, newFile.MaxFileSize, newFile.PingInterval); logFilesToParse.Add(scanner); IPBanLog.Debug("Adding log file to parse: {0}", pathAndMask); } else { IPBanLog.Debug("Ignoring log file path {0}, regex: {1}", pathAndMask, newFile.PlatformRegex); } } } }
private void ParseFirewallBlockRules() { string firewallBlockRuleString = null; GetConfig <string>("FirewallRules", ref firewallBlockRuleString); firewallBlockRuleString = (firewallBlockRuleString ?? string.Empty).Trim(); if (firewallBlockRuleString.Length == 0) { return; } IEnumerable <string> firewallBlockRuleList = firewallBlockRuleString.Trim().Split('\n').Select(s => s.Trim()).Where(s => s.Length != 0); foreach (string firewallBlockRule in firewallBlockRuleList) { string[] pieces = firewallBlockRule.Split(';'); if (pieces.Length == 5) { IPBanFirewallRule firewallBlockRuleObj = new IPBanFirewallRule { Block = (pieces[1].Equals("block", StringComparison.OrdinalIgnoreCase)), IPAddressRanges = pieces[2].Split(',').Select(p => IPAddressRange.Parse(p)).ToList(), Name = "EXTRA_" + pieces[0].Trim(), AllowPortRanges = pieces[3].Split(',').Select(p => PortRange.Parse(p)).Where(p => p.MinPort >= 0).ToList(), PlatformRegex = new Regex(pieces[4].Replace('*', '.'), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant) }; if (firewallBlockRuleObj.PlatformRegex.IsMatch(IPBanOS.Name)) { extraRules.Add(firewallBlockRuleObj); } } else { IPBanLog.Warn("Firewall block rule entry should have 3 comma separated pieces: name;ips;ports. Invalid entry: {0}", firewallBlockRule); } } }
/// <summary> /// Run the service /// </summary> /// <param name="requireAdministrator">True to require administrator, false otherwise</param> /// <returns>Exit code</returns> public async Task RunAsync(bool requireAdministrator = true) { if (requireAdministrator) { IPBanExtensionMethods.RequireAdministrator(); } if (args.Length != 0 && (args[0].Equals("info", StringComparison.OrdinalIgnoreCase) || args[0].Equals("-info", StringComparison.OrdinalIgnoreCase))) { IPBanLog.Warn("System info: {0}", IPBanOS.OSString()); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { await RunWindowsService(args); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { await RunLinuxService(args); } else { throw new PlatformNotSupportedException(); } }
/// <summary> /// Check if a user name is active on the local machine /// </summary> /// <param name="userName">User name to check</param> /// <returns>True if user name is active, false otherwise</returns> public static bool UserIsActive(string userName) { if (string.IsNullOrWhiteSpace(userName)) { return(false); } userName = userName.Trim(); try { if (isWindows) { // Windows: WMI SelectQuery query = new SelectQuery("Win32_UserAccount"); ManagementObjectSearcher searcher = new ManagementObjectSearcher(query); foreach (ManagementObject user in searcher.Get()) { if (user["Disabled"] is null || user["Disabled"].Equals(false)) { string possibleMatch = user["Name"]?.ToString(); if (possibleMatch != null && possibleMatch.Equals(userName, StringComparison.OrdinalIgnoreCase)) { return(true); } } } } else if (isLinux) { // Linux: /etc/passwd if (File.Exists("/etc/passwd")) { bool enabled = false; string[] lines; if (File.Exists("/etc/shadow")) { lines = File.ReadAllLines("/etc/shadow"); // example line: // root:!$1$Fp$SSSuo3L.xA5s/kMEEIloU1:18049:0:99999:7::: foreach (string[] pieces in lines.Select(l => l.Split(':')).Where(p => p.Length == 9)) { string checkUserName = pieces[0].Trim(); if (checkUserName.Equals(userName)) { string pwdHash = pieces[1].Trim(); if (pwdHash.Length != 0 && pwdHash[0] != '*' && pwdHash[0] != '!') { enabled = true; break; } else { return(false); } } } } if (enabled) { // user is OK in shadow file, check passwd file lines = File.ReadAllLines("/etc/passwd"); // example line: // root:x:0:0:root:/root:/bin/bash foreach (string[] pieces in lines.Select(l => l.Split(':')).Where(p => p.Length == 7)) { // x means shadow file is where the password is at string checkUserName = pieces[0].Trim(); string nologin = pieces[6]; if (checkUserName.Equals(userName) && nologin.IndexOf("nologin", StringComparison.OrdinalIgnoreCase) < 0 && !nologin.Contains("/bin/false")) { return(true); } } } } } // TODO: MAC } catch (Exception ex) { IPBanLog.Error("Error determining if user is active", ex); } return(false); }
static IPBanOS() { try { tempFolder = Path.GetTempPath(); if (string.IsNullOrWhiteSpace(tempFolder)) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { tempFolder = "c:\\temp"; } else { tempFolder = "/tmp"; } } Directory.CreateDirectory(tempFolder); // start off with built in version info, this is not as detailed or nice as we like, // so we try some other ways to get more detailed information Version = Environment.OSVersion.VersionString; Description = RuntimeInformation.OSDescription; // attempt to get detailed version info if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { isLinux = true; string tempFile = IPBanOS.GetTempFileName(); Process.Start("/bin/bash", "-c \"cat /etc/*release* > " + tempFile + "\"").WaitForExit(); System.Threading.Tasks.Task.Delay(100); // wait a small bit for file to really be closed string versionText = File.ReadAllText(tempFile).Trim(); IPBanExtensionMethods.FileDeleteWithRetry(tempFile); if (string.IsNullOrWhiteSpace(versionText)) { IPBanLog.Error(new IOException("Unable to load os version from /etc/*release* ...")); } else { Name = IPBanOS.Linux; FriendlyName = ExtractRegex(versionText, "^(Id|Distrib_Id)=(?<value>.*?)$", string.Empty); if (FriendlyName.Length != 0) { string codeName = ExtractRegex(versionText, "^(Name|Distrib_CodeName)=(?<value>.+)$", string.Empty); if (codeName.Length != 0) { FriendlyName += " - " + codeName; } Version = ExtractRegex(versionText, "^Version_Id=(?<value>.+)$", Version); } } } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { isWindows = true; processVerb = "runas"; Name = IPBanOS.Windows; string tempFile = IPBanOS.GetTempFileName(); // .net core WMI has a strange bug where WMI will not initialize on some systems // since this is the only place where WMI is used, we can just work-around it // with the wmic executable, which exists (as of 2018) on all supported Windows. StartProcessAndWait("cmd", "/C wmic path Win32_OperatingSystem get Caption,Version /format:table > \"" + tempFile + "\""); if (File.Exists(tempFile)) { // try up to 10 times to read the file for (int i = 0; i < 10; i++) { try { string[] lines = File.ReadAllLines(tempFile); IPBanExtensionMethods.FileDeleteWithRetry(tempFile); if (lines.Length > 1) { int versionIndex = lines[0].IndexOf("Version"); if (versionIndex >= 0) { FriendlyName = lines[1].Substring(0, versionIndex - 1).Trim(); Version = lines[1].Substring(versionIndex).Trim(); break; } } throw new IOException("Invalid file generated from wmic"); } catch (Exception ex) { if (i < 9) { System.Threading.Tasks.Task.Delay(200).Wait(); } else { IPBanLog.Error(ex, "Unable to load os version using wmic, trying wmi api..."); // last resort, try wmi api LoadVersionFromWmiApi(); } } } } else { // last resort, try wmi api LoadVersionFromWmiApi(); } } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { // TODO: Implement better for MAC Name = IPBanOS.Mac; FriendlyName = "OSX"; } else { Name = IPBanOS.Unknown; FriendlyName = "Unknown"; } } catch (Exception ex) { IPBanLog.Error("Error determining platform info", ex); } }
private IPBanConfig(string xml, IDnsLookup dns) { this.dns = dns; // deserialize with XmlDocument, the .net core Configuration class is quite buggy XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); foreach (XmlNode node in doc.SelectNodes("//appSettings/add")) { appSettings[node.Attributes["key"].Value] = node.Attributes["value"].Value; } GetConfig <int>("FailedLoginAttemptsBeforeBan", ref failedLoginAttemptsBeforeBan, 1, 50); GetConfig <bool>("ResetFailedLoginCountForUnbannedIPAddresses", ref resetFailedLoginCountForUnbannedIPAddresses); GetConfigArray <TimeSpan>("BanTime", ref banTimes, emptyTimeSpanArray); for (int i = 0; i < banTimes.Length; i++) { banTimes[i] = banTimes[i].Clamp(TimeSpan.FromMinutes(1.0), maxBanTimeSpan); } GetConfig <bool>("ClearBannedIPAddressesOnRestart", ref clearBannedIPAddressesOnRestart); GetConfig <TimeSpan>("ExpireTime", ref expireTime, TimeSpan.FromMinutes(1.0), maxBanTimeSpan); GetConfig <TimeSpan>("CycleTime", ref cycleTime, TimeSpan.FromSeconds(5.0), TimeSpan.FromMinutes(1.0), false); GetConfig <TimeSpan>("MinimumTimeBetweenFailedLoginAttempts", ref minimumTimeBetweenFailedLoginAttempts, TimeSpan.Zero, TimeSpan.FromSeconds(15.0), false); GetConfig <string>("FirewallRulePrefix", ref firewallRulePrefix); string whiteListString = GetConfig <string>("Whitelist", string.Empty); string whiteListRegexString = GetConfig <string>("WhitelistRegex", string.Empty); string blacklistString = GetConfig <string>("Blacklist", string.Empty); string blacklistRegexString = GetConfig <string>("BlacklistRegex", string.Empty); PopulateList(whiteList, whiteListRanges, ref whiteListRegex, whiteListString, whiteListRegexString); PopulateList(blackList, blackListRanges, ref blackListRegex, blacklistString, blacklistRegexString); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { expressionsFailure = new XmlSerializer(typeof(EventViewerExpressionsToBlock)).Deserialize(new XmlNodeReader(doc.SelectSingleNode("//ExpressionsToBlock"))) as EventViewerExpressionsToBlock; if (expressionsFailure != null) { foreach (EventViewerExpressionGroup group in expressionsFailure.Groups) { foreach (EventViewerExpression expression in group.Expressions) { expression.Regex = (expression.Regex?.ToString() ?? string.Empty).Trim(); } } } expressionsSuccess = new XmlSerializer(typeof(EventViewerExpressionsToNotify)).Deserialize(new XmlNodeReader(doc.SelectSingleNode("//ExpressionsToNotify"))) as EventViewerExpressionsToNotify; if (expressionsSuccess != null) { foreach (EventViewerExpressionGroup group in expressionsSuccess.Groups) { group.NotifyOnly = true; foreach (EventViewerExpression expression in group.Expressions) { expression.Regex = (expression.Regex?.ToString() ?? string.Empty).Trim(); } } } } else { expressionsFailure = new EventViewerExpressionsToBlock(); expressionsSuccess = new EventViewerExpressionsToNotify(); } try { if (new XmlSerializer(typeof(IPBanLogFilesToParse)).Deserialize(new XmlNodeReader(doc.SelectSingleNode("//LogFilesToParse"))) is IPBanLogFilesToParse logFilesToParse) { logFiles = logFilesToParse.LogFiles; } else { logFiles = emptyLogFilesToParseArray; } } catch (Exception ex) { IPBanLog.Error(ex); logFiles = new IPBanLogFileToParse[0]; } GetConfig <string>("ProcessToRunOnBan", ref processToRunOnBan); GetConfig <bool>("UseDefaultBannedIPAddressHandler", ref useDefaultBannedIPAddressHandler); // retrieve firewall configuration string[] firewallTypes = GetConfig <string>("FirewallType", string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries); foreach (string firewallOSAndType in firewallTypes) { string[] pieces = firewallOSAndType.Split(':'); if (pieces.Length == 2) { osAndFirewallType[pieces[0]] = pieces[1]; } } string userNameWhiteListString = GetConfig <string>("UserNameWhiteList", string.Empty); foreach (string userName in userNameWhiteListString.Split(',')) { string userNameTrimmed = userName.Normalize().ToUpperInvariant().Trim(); if (userNameTrimmed.Length > 0) { userNameWhitelist.Add(userNameTrimmed); } } string userNameWhiteListRegexString = GetConfig <string>("UserNameWhiteListRegex", string.Empty); if (!string.IsNullOrWhiteSpace(userNameWhiteListRegexString)) { userNameWhitelistRegex = new Regex(userNameWhiteListRegexString, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Singleline); } GetConfig <int>("UserNameWhiteListMinimumEditDistance", ref userNameWhitelistMaximumEditDistance); GetConfig <int>("FailedLoginAttemptsBeforeBanUserNameWhitelist", ref failedLoginAttemptsBeforeBanUserNameWhitelist); GetConfig <string>("GetUrlUpdate", ref getUrlUpdate); GetConfig <string>("GetUrlStart", ref getUrlStart); GetConfig <string>("GetUrlStop", ref getUrlStop); GetConfig <string>("GetUrlConfig", ref getUrlConfig); GetConfig <string>("ExternalIPAddressUrl", ref externalIPAddressUrl); // parse firewall block rules, one per line ParseFirewallBlockRules(); }
/// <summary> /// Get an ip address and user name out of text using regex. Regex may contain groups named source_[sourcename] to override the source. /// </summary> /// <param name="dns">Dns lookup to resolve ip addresses</param> /// <param name="regex">Regex</param> /// <param name="text">Text</param> /// <param name="ipAddress">Found ip address or null if none</param> /// <param name="userName">Found user name or null if none</param> /// <returns>True if a regex match was found, false otherwise</returns> public static IPAddressLogEvent GetIPAddressInfoFromRegex(IDnsLookup dns, Regex regex, string text) { const string customSourcePrefix = "source_"; bool foundMatch = false; string userName = null; string ipAddress = null; string source = null; int repeatCount = 1; Match repeater = Regex.Match(text, "message repeated (?<count>[0-9]+) times", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); if (repeater.Success) { repeatCount = int.Parse(repeater.Groups["count"].Value, CultureInfo.InvariantCulture); } foreach (Match m in regex.Matches(text)) { if (!m.Success) { continue; } // check for a user name Group userNameGroup = m.Groups["username"]; if (userNameGroup != null && userNameGroup.Success) { userName = (userName ?? userNameGroup.Value.Trim('\'', '\"', '(', ')', '[', ']', '{', '}', ' ', '\r', '\n')); } Group sourceGroup = m.Groups["source"]; if (sourceGroup != null && sourceGroup.Success) { source = (source ?? sourceGroup.Value.Trim('\'', '\"', '(', ')', '[', ']', '{', '}', ' ', '\r', '\n')); } if (string.IsNullOrWhiteSpace(source)) { foreach (Group group in m.Groups) { if (group.Success && group.Name != null && group.Name.StartsWith(customSourcePrefix)) { source = group.Name.Substring(customSourcePrefix.Length); } } } // check if the regex had an ipadddress group Group ipAddressGroup = m.Groups["ipaddress"]; if (ipAddressGroup is null) { ipAddressGroup = m.Groups["ipaddress_exact"]; } if (ipAddressGroup != null && ipAddressGroup.Success && !string.IsNullOrWhiteSpace(ipAddressGroup.Value)) { string tempIPAddress = ipAddressGroup.Value.Trim(); // in case of IP:PORT format, try a second time, stripping off the :PORT, saves having to do this in all // the different ip regex. int lastColon = tempIPAddress.LastIndexOf(':'); bool isValidIPAddress = IPAddress.TryParse(tempIPAddress, out IPAddress tmp); if (isValidIPAddress || (lastColon >= 0 && IPAddress.TryParse(tempIPAddress.Substring(0, lastColon), out tmp))) { ipAddress = tmp.ToString(); foundMatch = true; break; } // if we are parsing anything as ip address (including dns names) if (ipAddressGroup.Name == "ipaddress" && tempIPAddress != Environment.MachineName && tempIPAddress != "-") { // Check Host by name IPBanLog.Info("Parsing as IP failed, checking dns '{0}'", tempIPAddress); try { IPHostEntry entry = dns.GetHostEntryAsync(tempIPAddress).Sync(); if (entry != null && entry.AddressList != null && entry.AddressList.Length > 0) { ipAddress = entry.AddressList.FirstOrDefault().ToString(); IPBanLog.Info("Dns result '{0}' = '{1}'", tempIPAddress, ipAddress); foundMatch = true; break; } } catch { IPBanLog.Info("Parsing as dns failed '{0}'", tempIPAddress); } } } else { // found a match but no ip address, that is OK. foundMatch = true; } } return(new IPAddressLogEvent(ipAddress, userName, source, repeatCount, IPAddressEventType.FailedLogin) { FoundMatch = foundMatch }); }
/// <summary> /// Ping the files, this is normally done on a timer, but if you have passed a 0 second /// ping interval to the constructor, you must call this manually /// </summary> public void PingFiles() { try { if (pingTimer != null) { pingTimer.Enabled = false; } } catch { } foreach (WatchedFile file in GetCurrentWatchedFiles()) { try { // if file length has changed, ping the file bool delete = false; // ugly hack to force file to flush using (FileStream fs = new FileStream(file.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 16)) { try { if (fs.Length != 0) { fs.Position = fs.Length - 1; fs.ReadByte(); } } catch { } } long len = new FileInfo(file.FileName).Length; // if file has shrunk (deleted and recreated for example) reset positions to 0 if (len < file.LastLength || len < file.LastPosition) { file.LastPosition = 0; } // use file info for length compare to avoid doing a full file open if (len != file.LastLength) { using (FileStream fs = new FileStream(file.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 256)) { file.LastLength = len; delete = PingFile(file, fs); } } else { IPBanLog.Debug("Watched file {0} length has not changed", file.FileName); } if (delete) { try { File.Delete(file.FileName); } catch { // OK someone else might have it open, in which case we have no chance to delete } } } catch (Exception ex) { IPBanLog.Error(ex); } } try { if (pingTimer != null) { pingTimer.Enabled = true; } } catch { } }
private bool GetOrCreateRule(string ruleName, string remoteIPAddresses, NET_FW_ACTION_ action, IEnumerable <PortRange> allowedPorts = null) { remoteIPAddresses = (remoteIPAddresses ?? string.Empty).Trim(); bool emptyIPAddressString = string.IsNullOrWhiteSpace(remoteIPAddresses) || remoteIPAddresses == "*"; bool ruleNeedsToBeAdded = false; lock (policy) { recreateRule: INetFwRule rule = null; try { rule = policy.Rules.Item(ruleName); } catch { // ignore exception, assume does not exist } if (rule is null) { rule = Activator.CreateInstance(ruleType) as INetFwRule; rule.Name = ruleName; rule.Enabled = true; rule.Action = action; rule.Description = "Automatically created by IPBan"; rule.Direction = NET_FW_RULE_DIRECTION_.NET_FW_RULE_DIR_IN; rule.EdgeTraversal = false; rule.Grouping = "IPBan"; rule.LocalAddresses = "*"; rule.Profiles = int.MaxValue; // all ruleNeedsToBeAdded = true; } // do not ever set an empty string, Windows treats this as * which means everything if (!emptyIPAddressString) { try { PortRange[] allowedPortsArray = (allowedPorts?.ToArray()); if (allowedPortsArray != null && allowedPortsArray.Length != 0) { rule.Protocol = (int)NET_FW_IP_PROTOCOL_.NET_FW_IP_PROTOCOL_TCP; string localPorts; if (action == NET_FW_ACTION_.NET_FW_ACTION_BLOCK) { localPorts = IPBanFirewallUtility.GetPortRangeStringBlockExcept(allowedPortsArray); } else { localPorts = IPBanFirewallUtility.GetPortRangeStringAllow(allowedPortsArray); } rule.LocalPorts = localPorts; } else { try { rule.Protocol = (int)NET_FW_IP_PROTOCOL_.NET_FW_IP_PROTOCOL_ANY; } catch { // failed to set protocol to any, we are switching from tcp back to any without ports, the only option is to // recreate the rule if (!ruleNeedsToBeAdded) { policy.Rules.Remove(ruleName); goto recreateRule; } } } rule.RemoteAddresses = (remoteIPAddresses == "0.0.0.0/0,::/0" ? "*" : remoteIPAddresses); } catch (Exception ex) { // if something failed, do not create the rule emptyIPAddressString = true; IPBanLog.Error(ex); } } if (emptyIPAddressString || string.IsNullOrWhiteSpace(rule.RemoteAddresses) || (rule.RemoteAddresses == "*" && remoteIPAddresses != "0.0.0.0/0,::/0")) { // if no ip addresses, remove the rule as it will allow or block everything with an empty RemoteAddresses string try { rule = null; policy.Rules.Remove(ruleName); } catch { } } else if (ruleNeedsToBeAdded) { policy.Rules.Add(rule); } return(rule != null); } }