Created daemon python script.

This commit is contained in:
Artyom Titov 2026-08-26 21:44:58 +03:00
commit 5d9cd3c207
7 changed files with 651 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

293
Cargo.lock generated Normal file
View file

@ -0,0 +1,293 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bitflags"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "bumpalo"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "cc"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "find-msvc-tools"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de"
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [
"foldhash",
]
[[package]]
name = "hashlink"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32069d97bb81e38fa67eab65e3393bf804bb85969f2bc06bf13f64aef5aba248"
dependencies = [
"hashbrown 0.17.1",
]
[[package]]
name = "ipban"
version = "0.1.0"
dependencies = [
"rusqlite",
]
[[package]]
name = "js-sys"
version = "0.3.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
dependencies = [
"cfg-if",
"wasm-bindgen",
]
[[package]]
name = "libsqlite3-sys"
version = "0.38.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8"
dependencies = [
"pkg-config",
"vcpkg",
]
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "pkg-config"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rsqlite-vfs"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c"
dependencies = [
"hashbrown 0.16.1",
"thiserror",
]
[[package]]
name = "rusqlite"
version = "0.40.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23f2a97da3e3873c73cb2a2e71b35c40ff95e0b1eefa8d72d8499a6928c3b5b3"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
"sqlite-wasm-rs",
]
[[package]]
name = "rustversion"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "smallvec"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "sqlite-wasm-rs"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75"
dependencies = [
"cc",
"js-sys",
"rsqlite-vfs",
"wasm-bindgen",
]
[[package]]
name = "syn"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.3",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "wasm-bindgen"
version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn 2.0.119",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
dependencies = [
"unicode-ident",
]

7
Cargo.toml Normal file
View file

@ -0,0 +1,7 @@
[package]
name = "ipban"
version = "0.1.0"
edition = "2024"
[dependencies]
rusqlite = "0.40.0"

110
install.zsh Executable file
View file

@ -0,0 +1,110 @@
#!/bin/zsh
# synapsys:
# cout "output_type" "message"
# supported output types:
# inf war err
cout() {
local msg="$2"
local lead="[=]"
if [[ "$1" == "war" ]]; then
lead="[?]"
fi
if [[ "$1" == "inf" ]]; then
lead="[=]"
fi
if [[ "$1" == "err" ]]; then
lead="[!]"
fi
print -P "$lead" "$msg"
}
# synapsys:
# ask "default_ans" "message"
#
# if user answers YES ask returns 0
# if NO, returns 1
#
# ex: ask "y" "overwrite?"
# out: overwrite? [Y/n]:
ask() {
local def_ans="$1"
local promt
if [[ "$def_ans" == "y" ]]; then
promt="[Y/n]"
elif [[ "$def_ans" == "n" ]]; then
promt="[y/N]"
else
cout "err" "ask() got \$default_ans as $1"
exit 10
fi
local msg="$2"
while [[ true ]]; do
local ans=''
read -r "ans?$msg $promt: "
local ans="${ans:l}"
if [[ "$def_ans" == "y" ]]; then
if [[ -z "$ans" || "$ans" == "y" || "$ans" == "yes" ]]; then
return 0
elif [[ "$ans" == "n" || "$ans" == "no" ]]; then
return 1
fi
elif [[ "$def_ans" == "n" ]]; then
if [[ -z "$ans" || "$ans" == "n" || "$ans" == "no" ]]; then
return 1
elif [[ "$ans" == "y" || "$ans" == "YES" ]]; then
return 0
fi
fi
done
}
# synapsys:
# abort_install "reason"
abort_install() {
cout "err" "%F{red}Install aborted:%f\t$1"
exit 1
}
if [[ ! $(id -g ipban) ]]; then
sudo groupadd --system ipban
else
abort_install "Group %Bipban%b already exists"
fi
if [[ ! $(id -u ipban) ]]; then
sudo useradd \
--system \
--gid ipban \
--no-create-home \
--comment "ipban service" \
--home-dir / \
--shell /sbin/nologin \
ipban
else
abort_install "User %Bipban%b already exists"
fi
db_dir="/var/lib/ipban"
if [[ ! -d $db_dir ]]; then
sudo mkdir $db_dir
sudo chmod 775 $db_dir
sudo chown ipban:ipban $db_dir
else
if ask "n" "directory $db_dir already exists. rewrite?"; then
sudo rm -rf $db_dir
sudo mkdir $db_dir
sudo chmod 775 $db_dir
sudo chown ipban:ipban $db_dir
else
abort_install "User decided not to overwrite \'$db_dir\'. Which is requred to store banned ip\'s"
fi
fi
#TODO: create systemd service

171
py/daemon.py Executable file
View file

@ -0,0 +1,171 @@
#!/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()

48
src/main.rs Normal file
View file

@ -0,0 +1,48 @@
use rusqlite;
use std::{path::PathBuf, str::FromStr};
struct SqliteWrapper {
pub file: PathBuf,
connection: rusqlite::Connection,
}
impl SqliteWrapper {
pub fn new(&mut self){
self.file = PathBuf::new();
}
pub fn initialize(&mut self){
self.connection = rusqlite::Connection::open(self.file.to_str().unwrap());
}
}
fn main() {
let conn = rusqlite::Connection::open_in_memory().unwrap();
conn.execute(
"CREATE TABLE person (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
data BLOB)",
(),
)
conn.execute(
"INSERT INTO person (name, data) VALUES (?1, ?2)",
(&me.name, &me.data),
)
.unwrap();
let mut stmt = conn.prepare("SELECT id, name, data FROM person").unwrap();
let person_iterator = stmt.query_map([], |row| {
Ok(Person {
id: row.get(0).unwrap(),
name: row.get(1).unwrap(),
data: row.get(2).unwrap(),
})
});
for person in person_iterator.unwrap() {
println!("Found person {}", person.unwrap().to_str());
}
}

21
test.json Normal file
View file

@ -0,0 +1,21 @@
action
{
"ban": {
"1.2.3.4": {
"remark": "Cloudflare's web",
"reason": "bot"
},
"8.8.8.8": {
"remark": "",
"reason": ""
}
},
"uban": {
"1.3.3.7": {
"reason": "Misstake"
},
"1.0.0.1": {
"reason": "DNS"
}
}
}