92 lines
2.1 KiB
Python
92 lines
2.1 KiB
Python
#!/usr/bin/env python3
|
|
import sqlite3, subprocess, re, os, requests, time
|
|
|
|
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 geo(ip):
|
|
r = requests.get(f"http://ip-api.com/json/{ip}", timeout=5).json()
|
|
if r["status"] == "success":
|
|
return r["country"], r["lat"], r["lon"]
|
|
return None, None, None
|
|
|
|
def main():
|
|
db = sqlite3.connect(DB)
|
|
|
|
db.execute("CREATE TABLE IF NOT EXISTS failures (ts INTEGER, ip TEXT)")
|
|
|
|
db.execute("""
|
|
CREATE TABLE IF NOT EXISTS ip_geo (
|
|
ip TEXT PRIMARY KEY,
|
|
country TEXT,
|
|
lat REAL,
|
|
lon REAL
|
|
)
|
|
""")
|
|
|
|
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)
|
|
|
|
for line in out.splitlines():
|
|
if not line or not line[0].isdigit():
|
|
continue
|
|
|
|
ts_str, msg = line.split(" ", 1)
|
|
|
|
try:
|
|
ts = int(float(ts_str))
|
|
except ValueError:
|
|
continue
|
|
|
|
m = PATTERN.search(msg)
|
|
if not m:
|
|
continue
|
|
|
|
ip = m.group(1)
|
|
|
|
# store raw event
|
|
db.execute("INSERT INTO failures VALUES (?, ?)", (ts, ip))
|
|
|
|
# check cache
|
|
cached = db.execute(
|
|
"SELECT 1 FROM ip_geo WHERE ip = ?",
|
|
(ip,)
|
|
).fetchone()
|
|
|
|
if not cached:
|
|
country, lat, lon = geo(ip)
|
|
db.execute(
|
|
"INSERT OR REPLACE INTO ip_geo VALUES (?, ?, ?, ?)",
|
|
(ip, country, lat, lon)
|
|
)
|
|
time.sleep(0.2)
|
|
|
|
db.commit()
|
|
db.close()
|
|
|
|
# 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()
|