commit 37fbf43322a9295506b470a25bf6929b027b2f05 Author: Jonas Hahn Date: Sun Jun 7 21:26:11 2026 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b7be90 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.db +cursor.txt diff --git a/main.py b/main.py new file mode 100644 index 0000000..77cacb6 --- /dev/null +++ b/main.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +import sqlite3, subprocess, re, os + +DB = "sshd_failures.db" +STATE = "cursor.txt" + +PATTERN = re.compile(r"PAM: Authentication failure for root from ([0-9a-fA-F:.]+)") + +def get_cursor(): + return open(STATE).read().strip() if os.path.exists(STATE) else None + +def save_cursor(c): + open(STATE, "w").write(c) + +def main(): + db = sqlite3.connect(DB) + db.execute("CREATE TABLE IF NOT EXISTS failures(ts INTEGER, ip TEXT)") + + cmd = ["journalctl", "-u", "sshd", "-o", "short-unix", "--no-pager"] + cur = get_cursor() + if cur: + cmd += ["--after-cursor", cur] + + out = subprocess.check_output(cmd, text=True) + + total = 0 + parsed = 0 + inserted = 0 + + for line in out.splitlines(): + total += 1 + + if not line or not line[0].isdigit(): + continue + + parsed += 1 + + ts_str, msg = line.split(" ", 1) + + try: + ts = int(float(ts_str)) + except ValueError: + continue + + m = PATTERN.search(msg) + if m: + db.execute("INSERT INTO failures VALUES (?, ?)", (ts, m.group(1))) + inserted += 1 + + db.commit() + db.close() + + print(f"lines={total} parsed={parsed} inserted={inserted}") + + # save cursor + out = subprocess.check_output( + ["journalctl", "-u", "sshd", "-n", "1", "-o", "export"], + text=True + ) + + for l in out.splitlines(): + if l.startswith("__CURSOR="): + save_cursor(l.split("=", 1)[1]) + break + +if __name__ == "__main__": + main() diff --git a/stats.py b/stats.py new file mode 100644 index 0000000..f817dd8 --- /dev/null +++ b/stats.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +import sqlite3, datetime + +DB = "sshd_failures.db" + +db = sqlite3.connect(DB) + +for ip, count, last_ts in db.execute(""" + SELECT ip, COUNT(*), MAX(ts) + FROM failures + GROUP BY ip + ORDER BY COUNT(*) DESC +"""): + last = datetime.datetime.fromtimestamp(last_ts) + print(f"{ip:40} {count:5} last={last}") + +db.close()