From 3dfa0e9816a077235fca29924c77cbcf2b1cace9 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Sat, 15 Jun 2019 12:09:09 -0600 Subject: [PATCH] Tweak smarter mail log, other misc fixes --- Core/IPBanDB.cs | 43 +++++++++----- Core/IPBanFirewallUtility.cs | 105 ++++++++++++++++++--------------- Core/IPBanMemoryFirewall.cs | 5 ++ Core/IPBanService.cs | 41 ++++++++----- IPBan.csproj | 2 +- IPBanMain.cs | 16 +++-- IPBanTests/IPBanConfigTests.cs | 2 +- IPBanTests/IPBanTests.csproj | 2 +- app.config | 3 +- 9 files changed, 130 insertions(+), 89 deletions(-) diff --git a/Core/IPBanDB.cs b/Core/IPBanDB.cs index 7b70c3e9..c33a31a6 100644 --- a/Core/IPBanDB.cs +++ b/Core/IPBanDB.cs @@ -92,6 +92,16 @@ public IPBanDBTransaction(string connString) { DBConnection = new SqliteConnection(connString); DBConnection.Open(); + using (SqliteCommand command = DBConnection.CreateCommand()) + { + command.CommandText = "PRAGMA auto_vacuum = INCREMENTAL;"; + command.ExecuteNonQuery(); + } + using (SqliteCommand command = DBConnection.CreateCommand()) + { + command.CommandText = "PRAGMA journal_mode = WAL;"; + command.ExecuteNonQuery(); + } DBTransaction = DBConnection.BeginTransaction(transactionLevel); } @@ -130,10 +140,6 @@ public void Rollback() public const string FileName = "ipban.sqlite"; private const System.Data.IsolationLevel transactionLevel = System.Data.IsolationLevel.Serializable; - - // note that an ip that has a block count may not yet be in the ipAddressesAndBanDate dictionary - // for locking, always use ipAddressesAndBanDate - private readonly string dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FileName); private readonly string connString; private int ExecuteNonQuery(string cmdText, params object[] param) @@ -147,7 +153,7 @@ private int ExecuteNonQuery(SqliteConnection conn, SqliteTransaction tran, strin if (conn == null) { conn = new SqliteConnection(connString); - conn.Open(); + OpenConnection(conn); closeConn = true; } try @@ -174,10 +180,10 @@ private int ExecuteNonQuery(SqliteConnection conn, SqliteTransaction tran, strin private T ExecuteScalar(string cmdText, params object[] param) { - using (SqliteConnection connection = new SqliteConnection(connString)) + using (SqliteConnection conn = new SqliteConnection(connString)) { - connection.Open(); - using (SqliteCommand command = connection.CreateCommand()) + OpenConnection(conn); + using (SqliteCommand command = conn.CreateCommand()) { command.CommandText = cmdText; for (int i = 0; i < param.Length; i++) @@ -195,7 +201,7 @@ private SqliteDataReader ExecuteReader(string query, SqliteConnection conn, Sqli if (conn == null) { conn = new SqliteConnection(connString); - conn.Open(); + OpenConnection(conn); closeConnection = true; } SqliteCommand command = conn.CreateCommand(); @@ -208,6 +214,13 @@ private SqliteDataReader ExecuteReader(string query, SqliteConnection conn, Sqli return command.ExecuteReader((closeConnection ? System.Data.CommandBehavior.CloseConnection : System.Data.CommandBehavior.Default)); } + private void OpenConnection(SqliteConnection conn) + { + conn.Open(); + ExecuteNonQuery(conn, null, "PRAGMA auto_vacuum = INCREMENTAL;"); + ExecuteNonQuery(conn, null, "PRAGMA journal_mode = WAL;"); + } + private IPAddressEntry ParseIPAddressEntry(SqliteDataReader reader) { string ipAddress = reader.GetString(0); @@ -245,6 +258,7 @@ ON CONFLICT(IPAddress) private void Initialize() { + IPBanLog.Info("Initializing IPBan database at {0}", connString); SQLitePCL.Batteries.Init(); ExecuteNonQuery("PRAGMA auto_vacuum = INCREMENTAL;"); ExecuteNonQuery("PRAGMA journal_mode = WAL;"); @@ -268,8 +282,9 @@ private void Initialize() /// /// Constructor /// - public IPBanDB() + public IPBanDB(string dbPath = null) { + dbPath = (string.IsNullOrWhiteSpace(dbPath) ? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, FileName) : dbPath); connString = "Data Source=" + dbPath; Initialize(); } @@ -466,7 +481,7 @@ public int SetBannedIPAddresses(IEnumerable> ipAd int count = 0; using (SqliteConnection conn = new SqliteConnection(connString)) { - conn.Open(); + OpenConnection(conn); using (SqliteTransaction tran = conn.BeginTransaction(transactionLevel)) { foreach (KeyValuePair ipAddress in ipAddresses) @@ -494,7 +509,7 @@ public int SetIPAddressesState(IEnumerable ipAddresses, IPAddressState s SqliteConnection conn = (ipDBTransaction?.DBConnection ?? new SqliteConnection(connString)); if (commit) { - conn.Open(); + OpenConnection(conn); } SqliteTransaction tran = (ipDBTransaction?.DBTransaction ?? conn.BeginTransaction(transactionLevel)); int stateInt = (int)state; @@ -537,7 +552,7 @@ public IEnumerable EnumerateIPAddressesDelta(bool c { using (SqliteConnection conn = new SqliteConnection(connString)) { - conn.Open(); + OpenConnection(conn); using (SqliteTransaction tran = conn.BeginTransaction(transactionLevel)) { using (SqliteDataReader reader = ExecuteReader("SELECT IPAddressText, State FROM IPAddresses WHERE State IN (1, 2) ORDER BY IPAddressText", conn, tran)) @@ -629,7 +644,7 @@ public int DeleteIPAddresses(IEnumerable ipAddresses) using (SqliteConnection conn = new SqliteConnection(connString)) { - conn.Open(); + OpenConnection(conn); using (SqliteTransaction tran = conn.BeginTransaction(transactionLevel)) { foreach (string ipAddress in ipAddresses) diff --git a/Core/IPBanFirewallUtility.cs b/Core/IPBanFirewallUtility.cs index 71068d58..d285f8af 100644 --- a/Core/IPBanFirewallUtility.cs +++ b/Core/IPBanFirewallUtility.cs @@ -103,70 +103,77 @@ public static bool TryNormalizeIPAddress(this string ipAddress, out string norma /// Firewall public static IIPBanFirewall CreateFirewall(IReadOnlyDictionary osAndFirewall, string rulePrefix = null, IIPBanFirewall existing = null) { - bool foundFirewallType = false; - int priority = int.MinValue; - Type firewallType = typeof(IIPBanFirewall); - Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies().ToArray(); - Type[] types = assemblies.SelectMany(a => a.GetTypes()).ToArray(); - var q = - from fwType in types - where fwType.IsPublic && - fwType != firewallType && - firewallType.IsAssignableFrom(fwType) && - fwType.GetCustomAttribute() != null && - fwType.GetCustomAttribute().IsValid - select new { FirewallType = fwType, OS = fwType.GetCustomAttribute(), Name = fwType.GetCustomAttribute() }; - var array = q.ToArray(); - foreach (var result in array) + try { - // look up the requested firewall by os name - bool matchPriority = priority < result.OS.Priority; - if (matchPriority) + bool foundFirewallType = false; + int priority = int.MinValue; + Type firewallType = typeof(IIPBanFirewall); + Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies().ToArray(); + Type[] types = assemblies.SelectMany(a => a.GetTypes()).ToArray(); + var q = + from fwType in types + where fwType.IsPublic && + fwType != firewallType && + firewallType.IsAssignableFrom(fwType) && + fwType.GetCustomAttribute() != null && + fwType.GetCustomAttribute().IsValid + select new { FirewallType = fwType, OS = fwType.GetCustomAttribute(), Name = fwType.GetCustomAttribute() }; + var array = q.ToArray(); + foreach (var result in array) { - bool matchName = true; - if (osAndFirewall != null && osAndFirewall.Count != 0 && - (osAndFirewall.TryGetValue(IPBanOS.Name, out string firewallToUse) || osAndFirewall.TryGetValue("*", out firewallToUse))) - { - matchName = result.Name.Name.Equals(firewallToUse, StringComparison.OrdinalIgnoreCase); - } - if (matchName) + // look up the requested firewall by os name + bool matchPriority = priority < result.OS.Priority; + if (matchPriority) { - // if IsAvailable method is provided, attempt to call - MethodInfo available = result.FirewallType.GetMethod("IsAvailable", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); - if (available != null) + bool matchName = true; + if (osAndFirewall != null && osAndFirewall.Count != 0 && + (osAndFirewall.TryGetValue(IPBanOS.Name, out string firewallToUse) || osAndFirewall.TryGetValue("*", out firewallToUse))) { - try + matchName = result.Name.Name.Equals(firewallToUse, StringComparison.OrdinalIgnoreCase); + } + if (matchName) + { + // if IsAvailable method is provided, attempt to call + MethodInfo available = result.FirewallType.GetMethod("IsAvailable", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); + if (available != null) { - if (!Convert.ToBoolean(available.Invoke(null, null))) + try + { + if (!Convert.ToBoolean(available.Invoke(null, null))) + { + continue; + } + } + catch { continue; } } - catch - { - continue; - } + firewallType = result.FirewallType; + priority = result.OS.Priority; + foundFirewallType = true; } - firewallType = result.FirewallType; - priority = result.OS.Priority; - foundFirewallType = true; } } + if (firewallType == null) + { + throw new ArgumentException("Firewall is null, at least one type should implement IIPBanFirewall"); + } + else if (osAndFirewall.Count != 0 && !foundFirewallType) + { + string typeString = string.Join(',', osAndFirewall.Select(kv => kv.Key + ":" + kv.Value)); + throw new ArgumentException("Unable to find firewalls of types: " + typeString + ", osname: " + IPBanOS.Name); + } + if (existing != null && existing.GetType().Equals(firewallType)) + { + return existing; + } + return Activator.CreateInstance(firewallType, new object[] { rulePrefix }) as IIPBanFirewall; } - if (firewallType == null) - { - throw new ArgumentException("Firewall is null, at least one type should implement IIPBanFirewall"); - } - else if (osAndFirewall.Count != 0 && !foundFirewallType) - { - string typeString = string.Join(',', osAndFirewall.Select(kv => kv.Key + ":" + kv.Value)); - throw new ArgumentException("Unable to find firewalls of types: " + typeString + ", osname: " + IPBanOS.Name); - } - if (existing != null && existing.GetType().Equals(firewallType)) + catch (Exception ex) { - return existing; + throw new ArgumentException("Unable to create firewall, please double check your Firewall configuration property", ex); } - return Activator.CreateInstance(firewallType, new object[] { rulePrefix }) as IIPBanFirewall; } /// diff --git a/Core/IPBanMemoryFirewall.cs b/Core/IPBanMemoryFirewall.cs index 872d9747..e5502525 100644 --- a/Core/IPBanMemoryFirewall.cs +++ b/Core/IPBanMemoryFirewall.cs @@ -268,6 +268,11 @@ private string ScrubRuleNamePrefix(string ruleNamePrefix) return (RulePrefix + (ruleNamePrefix ?? string.Empty)).Trim('_'); } + public IPBanMemoryFirewall(string rulePrefix = null) + { + RulePrefix = (string.IsNullOrWhiteSpace(rulePrefix) ? RulePrefix : rulePrefix); + } + public void Update() { } diff --git a/Core/IPBanService.cs b/Core/IPBanService.cs index 6226c1b8..4bc035a9 100644 --- a/Core/IPBanService.cs +++ b/Core/IPBanService.cs @@ -69,16 +69,12 @@ private class IPAddressPendingEvent private System.Timers.Timer cycleTimer; private bool firewallNeedsBlockedIPAddressesUpdate; private bool gotStartUrl; + private IPBanDB ipDB; // batch failed logins every cycle private readonly List pendingFailedLogins = new List(); private readonly List pendingSuccessfulLogins = new List(); private readonly List pendingLogEvents = new List(); - - // note that an ip that has a block count may not yet be in the ipAddressesAndBanDate dictionary - // for locking, always use ipAddressesAndBanDate - private readonly IPBanDB ipDB; - private readonly object configLock = new object(); private readonly HashSet updaters = new HashSet(); private readonly HashSet logFilesToParse = new HashSet(); @@ -908,7 +904,10 @@ private void ProcessPendingLogEvents() IPBanLog.Error(ex); } ExecuteExternalProcessForBannedIPAddresses(bannedIPs); - UnblockIPAddresses(unbannedIPs); + if (unbannedIPs.Count != 0) + { + UnblockIPAddresses(unbannedIPs); + } } /// @@ -918,7 +917,6 @@ protected IPBanService() { OSName = IPBanOS.Name + (string.IsNullOrWhiteSpace(IPBanOS.FriendlyName) ? string.Empty : " (" + IPBanOS.FriendlyName + ")"); OSVersion = IPBanOS.Version; - ipDB = new IPBanDB(); } /// @@ -1121,7 +1119,7 @@ public void Dispose() { file.Dispose(); } - ipDB.Dispose(); + ipDB?.Dispose(); IPBanLog.Warn("Stopped IPBan service"); } catch @@ -1140,7 +1138,8 @@ public void Start() return; } - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + ipDB = new IPBanDB(DatabasePath); + if (UseWindowsEventViewer && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // attach Windows event viewer to the service EventViewer = new IPBanWindowsEventViewer(this); @@ -1422,30 +1421,35 @@ public static void DisposeIPBanTestService(IPBanService service) /// public IPBanConfig Config { get; private set; } + /// + /// Version of the software + /// + public string Version { get; set; } = Assembly.GetEntryAssembly().GetName().Version.ToString(); + /// /// Local ip address /// - public string LocalIPAddressString { get; private set; } + public string LocalIPAddressString { get; set; } /// /// Remote ip address /// - public string RemoteIPAddressString { get; private set; } + public string RemoteIPAddressString { get; set; } /// /// Fully qualified domain name /// - public string FQDN { get; private set; } + public string FQDN { get; set; } /// - /// Version of the software + /// Machine guid, null/empty for none /// - public string Version { get; private set; } = Assembly.GetEntryAssembly().GetName().Version.ToString(); + public string MachineGuid { get; set; } /// - /// Machine guid, null/empty for none + /// Override the sqlite database path, leave null for default /// - public string MachineGuid { get; set; } + public string DatabasePath { get; set; } /// /// External delegate to allow external config, whitelist, blacklist, etc. @@ -1482,6 +1486,11 @@ public static void DisposeIPBanTestService(IPBanService service) /// public IPBanWindowsEventViewer EventViewer { get; private set; } + /// + /// Whether to link up to the Windows event viewer on Start + /// + public bool UseWindowsEventViewer { get; set; } = true; + /// /// Log files to parse /// diff --git a/IPBan.csproj b/IPBan.csproj index 2404ca9d..cb873ebd 100644 --- a/IPBan.csproj +++ b/IPBan.csproj @@ -54,7 +54,7 @@ - + diff --git a/IPBanMain.cs b/IPBanMain.cs index 575b6f8c..8d9c5e93 100644 --- a/IPBanMain.cs +++ b/IPBanMain.cs @@ -36,21 +36,27 @@ public static class IPBanMain { public static async Task Main(string[] args) { - return await MainService(args); + return await MainService(args, out _); } public static Task MainService(string[] args) where T : IPBanService { - IPBanService service = IPBanService.CreateService(); + return MainService(args, out _); + } + + public static Task MainService(string[] args, out T service) where T : IPBanService + { + T _service = IPBanService.CreateService(); + service = _service; return MainService(args, (_args) => { - service.Start(); + _service.Start(); }, () => { - service.Stop(); + _service.Stop(); }, (_timeout) => { - return service.Wait(_timeout); + return _service.Wait(_timeout); }); } diff --git a/IPBanTests/IPBanConfigTests.cs b/IPBanTests/IPBanConfigTests.cs index dc3166d2..b50774fa 100644 --- a/IPBanTests/IPBanConfigTests.cs +++ b/IPBanTests/IPBanConfigTests.cs @@ -77,7 +77,7 @@ private void AssertLogFilesToParse(IPBanConfig cfg) "Windows", true, "MSExchange", "C:/Program Files/Smarter Tools/Smarter Mail/*.log\nC:/ Program Files(x86) / Smarter Tools / Smarter Mail/*.log\nC:/SmarterMail/logs/*.log\nC:/Smarter Mail/logs/*.log", - @"\[(?[^\]]+)\](\[.*?\]\s+)?((The domain given in the blocking rule EHLO command violates an EHLO SMTP\.\s*Any authentication attempts or RCPT commands will be rejected)|IP blocked by brute force abuse detection rule)", + @"\[(?[^\]]+)\](\[.*?\]\s+)?(The domain given in the EHLO command violates an EHLO SMTP|IP blocked by brute force abuse detection rule)", @"", "Windows", true, "SmarterMail", diff --git a/IPBanTests/IPBanTests.csproj b/IPBanTests/IPBanTests.csproj index 494b5d83..efe8a739 100644 --- a/IPBanTests/IPBanTests.csproj +++ b/IPBanTests/IPBanTests.csproj @@ -4,7 +4,7 @@ netcoreapp2.2 AnyCPU false - 1.4.7.0 + 1.4.8.0 1.4.7.0 DigitalRuby.IPBanTests DigitalRuby.IPBanTests diff --git a/app.config b/app.config index 5ba3cbf2..724cb9b0 100644 --- a/app.config +++ b/app.config @@ -120,8 +120,7 @@ [^\]]+)\](\[.*?\]\s+)?((The domain given in the blocking rule EHLO command violates an EHLO SMTP\.\s*Any authentication attempts or RCPT commands will be rejected)| - IP blocked by brute force abuse detection rule) + \[(?[^\]]+)\](\[.*?\]\s+)?(The domain given in the EHLO command violates an EHLO SMTP|IP blocked by brute force abuse detection rule) ]]>