From 7b3b5a4e3cc409fdc4ee17f00ca3d73af111b32f Mon Sep 17 00:00:00 2001 From: Ad Schellevis Date: Mon, 4 May 2026 17:25:33 +0200 Subject: [PATCH] security/q-feeds-connector: add optional locked mode in qfeedsctl.py for cron runners and wait for configfile changes when HTTP 401 is thrown. closes https://github.com/opnsense/plugins/issues/5416 This should prevent firewalls from spamming Q-Feeds infrastructure when either an empty or invalid token is specified. --- .../src/opnsense/scripts/qfeeds/lib/api.py | 14 +++++++--- .../src/opnsense/scripts/qfeeds/qfeedsctl.py | 26 +++++++++++++++++++ .../conf/actions.d/actions_qfeeds.conf | 2 +- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/security/q-feeds-connector/src/opnsense/scripts/qfeeds/lib/api.py b/security/q-feeds-connector/src/opnsense/scripts/qfeeds/lib/api.py index 749abeec9..c4285e811 100755 --- a/security/q-feeds-connector/src/opnsense/scripts/qfeeds/lib/api.py +++ b/security/q-feeds-connector/src/opnsense/scripts/qfeeds/lib/api.py @@ -29,16 +29,24 @@ from configparser import ConfigParser class QFeedsConfig: + config_filename = '/usr/local/etc/qfeeds.conf' api_key = None + conf_timestamp = None def __init__(self): - config_filename = '/usr/local/etc/qfeeds.conf' - if os.path.isfile(config_filename): + if os.path.isfile(self.config_filename): cnf = ConfigParser() - cnf.read(config_filename) + cnf.read(self.config_filename) if cnf.has_section('api') and cnf.has_option('api', 'key'): self.api_key = cnf.get('api', 'key').strip() + if self.conf_timestamp is None: + QFeedsConfig.conf_timestamp = os.stat(self.config_filename).st_mtime + + @classmethod + def has_changed(cls): + return os.path.isfile(cls.config_filename) and cls.conf_timestamp != os.stat(cls.config_filename).st_mtime + class Api: def __init__(self): diff --git a/security/q-feeds-connector/src/opnsense/scripts/qfeeds/qfeedsctl.py b/security/q-feeds-connector/src/opnsense/scripts/qfeeds/qfeedsctl.py index 7973d59f4..0fd72ce9b 100755 --- a/security/q-feeds-connector/src/opnsense/scripts/qfeeds/qfeedsctl.py +++ b/security/q-feeds-connector/src/opnsense/scripts/qfeeds/qfeedsctl.py @@ -27,10 +27,13 @@ """ import argparse +import fcntl +import time import sys import ujson from requests.exceptions import HTTPError, Timeout from lib import QFeedsActions +from lib.api import QFeedsConfig if __name__ == '__main__': @@ -38,8 +41,20 @@ if __name__ == '__main__': parser.add_argument('--target_dir', default='/var/db/qfeeds-tables') parser.add_argument('-f', help='forced (auto index)' , default=False, action='store_true') parser.add_argument('-v', help='verbose output' , default=False, action='store_true') + parser.add_argument('-l', help='lock operation' , default=False, action='store_true') parser.add_argument("action", choices=QFeedsActions.list_actions(), nargs='*') args = parser.parse_args() + + fhandle = None + if args.l: + lck_filename = '/tmp/qfeeds_prc.LCK' + fhandle = open(lck_filename, 'a+') + try: + fcntl.flock(fhandle, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError: + print('already busy, exit') + sys.exit(0) + if args.v: # verbose mode import http.client as http_client @@ -51,6 +66,14 @@ if __name__ == '__main__': print(msg) except HTTPError as exc: print('exit with HTTPError %d (%s)' % (exc.response.status_code, exc.response.text)) + if exc.response.status_code == 401 and 'update' in args.action: + print('batch mode - wait for configuration update or timeout') + t_start = time.time() + while not QFeedsConfig.has_changed(): + if time.time() - t_start > 3600: + print('timeout waiting for config change') + break + time.sleep(5) sys.exit(-1) except Timeout as exc: print('timeout reaching api endpoint') @@ -61,3 +84,6 @@ if __name__ == '__main__': except ujson.JSONDecodeError: print("JSON decode error") sys.exit(-1) + finally: + if fhandle: + fcntl.flock(fhandle, fcntl.LOCK_UN) diff --git a/security/q-feeds-connector/src/opnsense/service/conf/actions.d/actions_qfeeds.conf b/security/q-feeds-connector/src/opnsense/service/conf/actions.d/actions_qfeeds.conf index 9558b9c88..44e332070 100644 --- a/security/q-feeds-connector/src/opnsense/service/conf/actions.d/actions_qfeeds.conf +++ b/security/q-feeds-connector/src/opnsense/service/conf/actions.d/actions_qfeeds.conf @@ -6,7 +6,7 @@ message:reconfigure QFeeds errors:no [update] -command:/usr/local/opnsense/scripts/qfeeds/qfeedsctl.py update +command:/usr/local/opnsense/scripts/qfeeds/qfeedsctl.py -l update parameters: type:script_output message:update QFeeds