ipban/py/daemon.py

171 lines
5.3 KiB
Python
Executable file

#!/usr/bin/env python
import sqlite3
import ipaddress
from datetime import date, datetime, timezone
import os
import signal
import socket
from pathlib import Path
import json
from nftables import Nftables
from collections.abc import Iterable
#create table and its internal content
def create_nft_table():
nft = Nftables()
#rc, output, error = nft.cmd("add table inet ipban { flags owner; }")
rc, output, error = nft.cmd("add table inet ipban")
rc, output, error = nft.cmd(f"add set inet ipban banned_ipv4 {{ type ipv4_addr; }}")
rc, output, error = nft.cmd(f"add set inet ipban banned_ipv6 {{ type ipv6_addr; }}")
rc, output, error = nft.cmd("add chain inet ipban in_ban { type filter hook input priority filter; }")
rc, output, error = nft.cmd("add rule inet ipban in_ban ip saddr @banned_ipv4 drop")
rc, output, error = nft.cmd("add rule inet ipban in_ban ip6 saddr @banned_ipv6 drop")
def destroy_nft_table():
nft = Nftables()
rc, output, error = nft.cmd("destroy table inet ipban")
#delete old table, create new, and fill with ip's
def flush_nft(ips : Iterable[ipaddress.IPv4Address]):
nft = Nftables()
ips_str = ""
for ip in ips:
ips_str += f"{str(ip)}, "
ips_str = ips_str[:-2]
if ips_str == "":
return
rc, output, error = nft.cmd("destroy set inet ipban banned_ipv4")
rc, output, error = nft.cmd(f"""add set inet ipban banned_ipv4 {{type ipv4_addr;
elements = {{ {ips_str} }}; }}""")
#add list of ips to set in nft
def add_to_end_nft(ips: Iterable[ipaddress.IPv4Address]):
nft = Nftables()
ips_str = ""
for ip in ips:
ips_str += f"{str(ip)}, "
ips_str = ips_str[:-2]
if ips_str == "":
return
rc, output, error = nft.cmd(f"add element inet ipban banned_ipv4 {{ {ips_str} }}")
def init_nft(sql_conn: sqlite3.Connection):
rows = sql_conn.execute("""SELECT ip FROM banned
""").fetchall()
flush_nft([row[0] for row in rows])
#add selected ip to db table banned
def make_ban(sql_conn: sqlite3.Connection, ip: ipaddress.IPv4Address, remark = "", reason = "") -> bool:
#checking if ip is already banned
row = sql_conn.execute(f"""SELECT id FROM {ban_t_name}
WHERE ip = ? LIMIT 1""", (str(ip),)).fetchone()
print(row)
if row is not None:
return False #already banned
ban_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
sql_conn.execute(f"""INSERT INTO {ban_t_name} (ban_time,ip,remark,reason) VALUES (?, ?, ?, ?)""",
(str(ban_time), str(ip), remark, reason))
sql_conn.commit()
return True
#what to do in case of req_type=="action"
#TODO: protect from situation: ban "1.1.1.1", uban "1.1.1.1"
def action(sql_conn: sqlite3.Connection, conn: socket.socket, data: str):
data = data[data.find('\n')+1:] #strip req_type
data_json = json.loads(data)
ips_to_ban = list()
for ip,creds in data_json["ban"].items():
ip_addr = ipaddress.IPv4Address(ip)
remark = creds["remark"]
reason = creds["reason"]
make_ban(sql_conn, ip_addr, remark, reason)
ips_to_ban.append(ip)
for ip, creds in data_json["uban"].items():
print("Not implemented yet")
break
add_to_end_nft(ips_to_ban)
def list_ips(sql_conn: sqlite3.Connection, conn: socket.socket, data: str):
data = data[data.find('\n')+1:] #strip req_type
def recv_all(conn: socket.socket) -> bytes:
chunks = []
while True:
data = conn.recv(4096)
if not data:
break
chunks.append(data)
return b"".join(chunks)
#initilise UNIX socket and do someting like http handler
def run_server(socket_path: Path, sql_conn: sqlite3.Connection):
if os.path.exists(socket_path):
os.unlink(socket_path)
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server.bind(socket_path.as_posix())
server.listen()
try:
while True:
conn,_ = server.accept()
try:
data=recv_all(conn)
if not data:
continue
data = data.decode()
req_type = data.split('\n')[0]
match req_type:
case "action":
action(sql_conn, conn, data)
case "list":
list_ips(sql_conn, conn, data)
finally:
conn.close()
finally:
server.close()
if os.path.exists(socket_path):
os.unlink(socket_path)
def stop_handler(sugnum, frame):
destroy_nft_table()
os._exit(0)
if __name__ == "__main__":
signal.signal(signal.SIGINT, stop_handler)
signal.signal(signal.SIGTERM, stop_handler)
sql_conn = sqlite3.connect("/var/lib/ipban/db.file")
ban_t_name="banned"
sql_conn.execute(f"""CREATE TABLE IF NOT EXISTS {ban_t_name}
(id INTEGER PRIMARY KEY AUTOINCREMENT,
ban_time TEXT NOT NULL,
ip TEXT NOT NULL,
remark TEXT,
reason TEXT)""")
create_nft_table()
init_nft(sql_conn)
#flush_nft([ ipaddress.IPv4Address("123.134.124.1"), ipaddress.IPv4Address("8.8.8.8")])
sock_path = Path("/run/ipban/ipban.sock")
sock_path.parent.mkdir(parents=True,exist_ok=True)
run_server(sock_path, sql_conn)
destroy_nft_table()