private bool PingFile(WatchedFile file, FileStream fs) { const int maxCountBeforeNewline = 1024; int b; long lastNewlinePos = -1; long end = Math.Min(file.LastLength, fs.Length); int countBeforeNewline = 0; fs.Position = file.LastPosition; IPBanLog.Info("Processing watched file {0}, len = {1}, pos = {2}", file.FileName, file.LastLength, file.LastPosition); while (fs.Position < end && countBeforeNewline++ != maxCountBeforeNewline) { // read until last \n is found b = fs.ReadByte(); if (b == '\n') { lastNewlinePos = fs.Position - 1; countBeforeNewline = 0; } } if (countBeforeNewline == maxCountBeforeNewline) { throw new InvalidOperationException($"Log file '{file.FileName}' may not be a plain text new line delimited file"); } if (lastNewlinePos > -1) { try { // we could read line by line by going one byte at a time, but the hope here is that by taking // advantage of stream reader and binary reader read bytes we can get some improved cpu usage // at the expense of having to store all the bytes in memory for a small time fs.Position = file.LastPosition; byte[] bytes = new BinaryReader(fs).ReadBytes((int)(lastNewlinePos - fs.Position)); using (StreamReader reader = new StreamReader(new MemoryStream(bytes), Encoding.UTF8)) { string line; while ((line = reader.ReadLine()) != null) { line = line.Trim(); if (!OnProcessLine(line) || (ProcessLine != null && !ProcessLine(line))) { break; } } } } finally { // set file position for next ping fs.Position = file.LastPosition = ++lastNewlinePos; } } return(maxFileSize > 0 && fs.Length > maxFileSize); }
/// <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()); }
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 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 }); }