M beryllia/__init__.py => beryllia/__init__.py +6 -2
@@ 266,10 266,14 @@ class Server(BaseServer):
kline_id = await self.database.kline.find_active(mask)
if kline_id is not None:
- found = await self.database.kline_reject.find(
+ db = self.database
+ found = await db.kline_reject.find(
kline_id, nickname, username, hostname, ip
)
- if found is None:
+ others = await db.kline_reject.find_by_hostname(
+ kline_id, hostname
+ )
+ if found is None and len(others) < self._config.rejects:
await self.database.kline_reject.add(
kline_id, nickname, username, hostname, ip
)
M beryllia/config.py => beryllia/config.py +2 -0
@@ 14,6 14,7 @@ class Config(object):
password: str
channels: Sequence[str]
log: Optional[str]
+ rejects: int
sasl: Tuple[str, str]
oper: Tuple[str, str, str]
@@ 50,6 51,7 @@ def load(filepath: str):
config_yaml["password"],
config_yaml["channels"],
config_yaml.get("log", None),
+ config_yaml["rejects"],
(config_yaml["sasl"]["username"], config_yaml["sasl"]["password"]),
(oper_name, oper_file, oper_pass),
config_yaml["database"]["user"],
M beryllia/database/db_kline_reject.py => beryllia/database/db_kline_reject.py +16 -0
@@ 101,6 101,22 @@ class KLineRejectTable(KLineKillTable):
return [DBKLineReject(*row) for row in rows]
+ async def find_by_hostname(self,
+ kline_id: int,
+ hostname: str
+ ) -> Collection[int]:
+
+ query = """
+ SELECT id
+ FROM kline_reject
+ WHERE kline_id = $1
+ AND search_host = $2
+ """
+ search_host = str(self.to_search(hostname, SearchType.HOST))
+ async with self.pool.acquire() as conn:
+ rows = await conn.fetch(query, kline_id, search_host)
+ return [r[0] for r in rows]
+
async def set_kline(self,
reject_id: int,
kline_id: int):
M config.example.yaml => config.example.yaml +2 -0
@@ 7,6 7,8 @@ nickname: beryllia
password: beryllia:hunter2
channels: []
log: "#libera-klines"
+# maximum kline rejections to store per k-line
+rejects: 20
sasl:
username: beryllia