diff --git a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Dnsbl.xml b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Dnsbl.xml
index 188564ee6..ba05d656e 100644
--- a/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Dnsbl.xml
+++ b/dns/dnscrypt-proxy/src/opnsense/mvc/app/models/OPNsense/Dnscryptproxy/Dnsbl.xml
@@ -19,6 +19,7 @@
Easyprivacy List
NoCoin List
PornTop1M List
+ Q-Feeds (when installed)
Simple Ad List
Simple Tracker List
Steven Black List
diff --git a/dns/dnscrypt-proxy/src/opnsense/scripts/OPNsense/Dnscryptproxy/dnsbl.sh b/dns/dnscrypt-proxy/src/opnsense/scripts/OPNsense/Dnscryptproxy/dnsbl.sh
index 23bb78589..77420eef0 100755
--- a/dns/dnscrypt-proxy/src/opnsense/scripts/OPNsense/Dnscryptproxy/dnsbl.sh
+++ b/dns/dnscrypt-proxy/src/opnsense/scripts/OPNsense/Dnscryptproxy/dnsbl.sh
@@ -154,6 +154,13 @@ simpletrack() {
rm ${WORKDIR}/simpletrack-raw
}
+qfeeds() {
+ # Q-Feeds List
+ if [ -f "/usr/local/etc/dnscrypt-proxy/blacklist-qfeeds.txt" ] && [ -s "/usr/local/etc/dnscrypt-proxy/blacklist-qfeeds.txt" ]; then
+ sed "/\.$/d" /usr/local/etc/dnscrypt-proxy/blacklist-qfeeds.txt | sed "/^#/d" | sed "/\_/d" | sed "/^[[:space:]]*$/d" | sed "/\.\./d" | sed "s/^\.//g" > ${WORKDIR}/qfeeds
+ fi
+}
+
install() {
# Put all files in correct format
for FILE in $(find ${WORKDIR} -type f); do
@@ -222,6 +229,9 @@ for CAT in $(echo ${DNSBL} | tr ',' ' '); do
yy)
yoyo
;;
+ qf)
+ qfeeds
+ ;;
esac
done
diff --git a/security/q-feeds-connector/Makefile b/security/q-feeds-connector/Makefile
index b2bf170b2..cf53d91cd 100644
--- a/security/q-feeds-connector/Makefile
+++ b/security/q-feeds-connector/Makefile
@@ -1,5 +1,5 @@
PLUGIN_NAME= q-feeds-connector
-PLUGIN_VERSION= 1.3
+PLUGIN_VERSION= 1.4
PLUGIN_TIER= 2
PLUGIN_COMMENT= Connector for Q-Feeds threat intel
PLUGIN_MAINTAINER= devel@qfeeds.com
diff --git a/security/q-feeds-connector/pkg-descr b/security/q-feeds-connector/pkg-descr
index c336c3b3d..02c1a8ae8 100644
--- a/security/q-feeds-connector/pkg-descr
+++ b/security/q-feeds-connector/pkg-descr
@@ -2,6 +2,14 @@ Connector for Q-Feeds threat intel
Plugin Changelog
================
+1.4
+
+* Feature: Added DNSCrypt-Proxy
+
+1.3
+
+* Widget: Added license info
+* Events: added source and destination port
1.3
diff --git a/security/q-feeds-connector/src/opnsense/scripts/dnscryptproxy/blocklists/qfeeds_bl.py b/security/q-feeds-connector/src/opnsense/scripts/dnscryptproxy/blocklists/qfeeds_bl.py
new file mode 100644
index 000000000..ab04f8192
--- /dev/null
+++ b/security/q-feeds-connector/src/opnsense/scripts/dnscryptproxy/blocklists/qfeeds_bl.py
@@ -0,0 +1,92 @@
+#!/usr/local/bin/python3
+
+"""
+ Copyright (c) 2025 Deciso B.V.
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+"""
+
+import os
+import glob
+import re
+
+# Check if 'qf' is selected in DNSCrypt-proxy DNSBL configuration
+def is_qf_selected():
+ rc_conf_file = '/etc/rc.conf.d/dnscrypt_proxy'
+ if os.path.exists(rc_conf_file):
+ try:
+ with open(rc_conf_file, 'r') as f:
+ for line in f:
+ # Look for dnscrypt_proxy_dnsbl="..." line
+ match = re.search(r'dnscrypt_proxy_dnsbl="([^"]*)"', line)
+ if match:
+ dnsbl_list = match.group(1)
+ # Check if 'qf' is in the comma-separated list
+ return 'qf' in [x.strip() for x in dnsbl_list.split(',')]
+ except Exception:
+ pass
+ return False
+
+# Q-Feeds domain files directory
+qfeeds_tables_dir = '/var/db/qfeeds-tables'
+
+# Automatically find all domain files (*_domains.txt) in qfeeds-tables directory
+# This will include malware_domains.txt, phishing_domains.txt, etc.
+qfeeds_filenames = []
+if os.path.isdir(qfeeds_tables_dir):
+ # Find all files ending with _domains.txt (e.g., malware_domains.txt, phishing_domains.txt)
+ pattern = os.path.join(qfeeds_tables_dir, '*_domains.txt')
+ qfeeds_filenames = sorted(glob.glob(pattern))
+
+# Collect q-feeds domains
+qfeeds_domains = set()
+for filename in qfeeds_filenames:
+ if os.path.exists(filename):
+ with open(filename, 'r') as f_in:
+ for line in f_in:
+ domain = line.strip()
+ if domain:
+ qfeeds_domains.add(domain)
+
+# Write q-feeds domains to blacklist-qfeeds.txt
+# dnscrypt-proxy's dnsbl.sh qfeeds() function will read this file when 'qf' is selected in DNSBL config
+qfeeds_blacklist_file = '/usr/local/etc/dnscrypt-proxy/blacklist-qfeeds.txt'
+dnscrypt_proxy_dir = '/usr/local/etc/dnscrypt-proxy'
+
+# Only proceed if DNSCrypt-proxy directory exists (plugin is installed) AND 'qf' is selected
+if os.path.isdir(dnscrypt_proxy_dir) and is_qf_selected():
+ if qfeeds_domains:
+ # Write q-feeds domains to separate file
+ with open(qfeeds_blacklist_file, 'w') as f_out:
+ for domain in sorted(qfeeds_domains):
+ f_out.write("%s\n" % domain)
+ else:
+ # Remove q-feeds blacklist file if no domains available
+ if os.path.exists(qfeeds_blacklist_file):
+ os.remove(qfeeds_blacklist_file)
+elif os.path.isdir(dnscrypt_proxy_dir):
+ # DNSCrypt-proxy is installed but 'qf' is not selected - remove the file if it exists
+ if os.path.exists(qfeeds_blacklist_file):
+ os.remove(qfeeds_blacklist_file)
+
+
diff --git a/security/q-feeds-connector/src/opnsense/scripts/qfeeds/lib/__init__.py b/security/q-feeds-connector/src/opnsense/scripts/qfeeds/lib/__init__.py
index f0f029c1d..87ba013dc 100755
--- a/security/q-feeds-connector/src/opnsense/scripts/qfeeds/lib/__init__.py
+++ b/security/q-feeds-connector/src/opnsense/scripts/qfeeds/lib/__init__.py
@@ -46,6 +46,7 @@ class QFeedsActions:
'show_index',
'firewall_load',
'unbound_load',
+ 'dnscryptproxy_load',
'update',
'stats',
'logs'
@@ -121,6 +122,24 @@ class QFeedsActions:
subprocess.run(['/usr/local/sbin/configctl', 'unbound', 'dnsbl'])
yield 'update unbound blocklist'
+ def dnscryptproxy_load(self):
+ script_path = '/usr/local/opnsense/scripts/dnscryptproxy/blocklists/qfeeds_bl.py'
+ dnscrypt_proxy_dir = '/usr/local/etc/dnscrypt-proxy'
+ if os.path.exists(script_path) and os.path.isdir(dnscrypt_proxy_dir):
+ subprocess.run([script_path], capture_output=True, text=True)
+ # Trigger dnscrypt-proxy DNSBL update to merge blacklist-qfeeds.txt
+ # Only if DNSCrypt-proxy is installed (directory exists)
+ result = subprocess.run(['/usr/local/sbin/configctl', 'dnscryptproxy', 'dnsbl'],
+ capture_output=True, text=True)
+ if result.returncode == 0:
+ yield 'update dnscrypt-proxy blocklist'
+ else:
+ yield 'dnscrypt-proxy not available'
+ elif not os.path.isdir(dnscrypt_proxy_dir):
+ yield 'dnscrypt-proxy not installed'
+ else:
+ yield 'dnscrypt-proxy blocklist script not found'
+
def update(self):
update_sleep = 99999
try:
@@ -136,7 +155,7 @@ class QFeedsActions:
if do_update:
if 0 < update_sleep <= 300:
time.sleep(update_sleep)
- for action in ['fetch_index', 'fetch', 'firewall_load', 'unbound_load']:
+ for action in ['fetch_index', 'fetch', 'firewall_load', 'unbound_load', 'dnscryptproxy_load']:
yield from getattr(self, action)()
def stats(self):
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 e2fba096e..9558b9c88 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
@@ -1,5 +1,5 @@
[reconfigure]
-command:/usr/local/opnsense/scripts/qfeeds/qfeedsctl.py fetch_index fetch firewall_load unbound_load && echo 'EXIT OK'
+command:/usr/local/opnsense/scripts/qfeeds/qfeedsctl.py fetch_index fetch firewall_load unbound_load dnscryptproxy_load && echo 'EXIT OK'
parameters:
type:script_output
message:reconfigure QFeeds