From: Michael Tremer Date: Fri, 5 Dec 2025 15:37:09 +0000 (+0000) Subject: dnsbl: Add a command to create a new list X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4bc3690dc8782356529b8fd2ef9f7e46cd4346e1;p=dbl.git dnsbl: Add a command to create a new list Signed-off-by: Michael Tremer --- diff --git a/Makefile.am b/Makefile.am index 4d7a130..ddbfcb2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -52,7 +52,9 @@ SED_PROCESS = \ dist_pkgpython_PYTHON = \ src/dnsbl/__init__.py \ src/dnsbl/i18n.py \ - src/dnsbl/logger.py + src/dnsbl/lists.py \ + src/dnsbl/logger.py \ + src/dnsbl/util.py # ------------------------------------------------------------------------------ diff --git a/src/dnsbl/__init__.py b/src/dnsbl/__init__.py index b5a0ea1..4b108be 100644 --- a/src/dnsbl/__init__.py +++ b/src/dnsbl/__init__.py @@ -19,6 +19,7 @@ ############################################################################### import configparser +import functools import logging import sqlmodel @@ -28,6 +29,9 @@ from . import logger # Setup logging log = logging.getLogger(__name__) +# Import sub-modules +from . import lists + class Backend(object): def __init__(self, config=None, debug=False): self.debug = debug @@ -70,6 +74,16 @@ class Backend(object): logging_name=log.name, ) + def session(self): + """ + Returns a new database session + """ + return sqlmodel.Session(self.db) + + @functools.cached_property + def lists(self): + return lists.Lists(self) + def update_sources(self): """ Updates all sources diff --git a/src/dnsbl/lists.py b/src/dnsbl/lists.py new file mode 100644 index 0000000..3f683d4 --- /dev/null +++ b/src/dnsbl/lists.py @@ -0,0 +1,107 @@ +############################################################################### +# # +# dnsbl - A DNS Blacklist Compositor For IPFire # +# Copyright (C) 2025 IPFire Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +import datetime +import sqlmodel + +from . import util + +class Lists(object): + def __init__(self, backend): + self.backend = backend + + def get_by_slug(self, slug): + stmt = ( + sqlmodel + .select( + List, + ) + .where( + List.slug == slug, + List.deleted_at == None, + ) + ) + + with self.backend.session() as session: + result = session.execute(stmt) + + return result.scalar_one_or_none() + + def _make_slug(self, name): + i = 0 + + while True: + slug = util.slugify(name, i) + + # Skip if the list already exists + if self.get_by_slug(slug): + i += 1 + continue + + return slug + + def create(self, name, created_by, license): + """ + Creates a new list + """ + slug = self._make_slug(name) + + # Create a new list + with self.backend.session() as session: + list = List( + name = name, + slug = slug, + created_by = created_by, + license = license, + ) + session.add(list) + session.commit() + + return list + + +class List(sqlmodel.SQLModel, table=True): + __tablename__ = "lists" + + # ID + id : int = sqlmodel.Field(primary_key=True) + + # Name + name : str + + # Slug + slug : str + + # Created At + created_at : datetime.datetime = sqlmodel.Field( + sa_column_kwargs = {"server_default" : sqlmodel.text("CURRENT_TIMESTAMP")} + ) + + # Created By + created_by : str + + # Deleted At + deleted_at : datetime.datetime | None + + # Deleted By + deleted_by : str | None + + # License + license : str diff --git a/src/dnsbl/util.py b/src/dnsbl/util.py new file mode 100644 index 0000000..a35aa3c --- /dev/null +++ b/src/dnsbl/util.py @@ -0,0 +1,45 @@ +############################################################################### +# # +# dnsbl - A DNS Blacklist Compositor For IPFire # +# Copyright (C) 2025 IPFire Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +############################################################################### + +import re +import unicodedata + +def slugify(text, iteration=None): + # Append the iteration + if iteration: + text = "%s-%s" % (text, iteration) + + # Normalize Unicode characters (e.g., é → e) + text = unicodedata.normalize("NFKD", text) + text = text.encode("ascii", "ignore").decode("ascii") + + # Lowercase + text = text.lower() + + # Replace non-alphanumeric characters with hyphens + text = re.sub(r"[^a-z0-9]+", "-", text) + + # Remove leading/trailing hyphens + text = text.strip("-") + + # Collapse multiple hyphens + text = re.sub(r"-+", "-", text) + + return text diff --git a/src/scripts/dnsbl.in b/src/scripts/dnsbl.in index 1c99f59..ef4556d 100644 --- a/src/scripts/dnsbl.in +++ b/src/scripts/dnsbl.in @@ -22,6 +22,7 @@ import argparse import dnsbl import logging +import os import sys # i18n @@ -45,6 +46,16 @@ class CLI(object): # Show Version parser.add_argument("--version", action="version", version="%(prog)s @VERSION@") + # create-list + create_list = subparsers.add_parser("create-list", help=_("Create a new list")) + create_list.add_argument("--name", required=True, + help=_("The name of the list")) + create_list.add_argument("--created-by", required=True, + default=os.environ.get("USER"), help=_("The creator of the list")) + create_list.add_argument("--license", required=True, + help=_("The license of the list")) + create_list.set_defaults(func=self.__create_list) + # update update = subparsers.add_parser("update", help=_("Update sources")) update.set_defaults(func=self.__update) @@ -83,6 +94,16 @@ class CLI(object): # Otherwise just exit sys.exit(0) + def __create_list(self, backend, args): + """ + Creates a new list + """ + backend.lists.create( + name = args.name, + created_by = args.created_by, + license = args.license, + ) + def __update(self, backend, args): """ Updates all sources